"""Acesso ao SQLite e migrações incrementais de schema."""
from __future__ import annotations

import sqlite3
from pathlib import Path

from flask import Flask, g

from .config import get_config

SCHEMA_VERSION = 4

# Migrações aplicadas em ordem: {versão_destino: [sql, ...]}
# Cada instrução é tolerante a "já aplicada" (ver migrate()).
MIGRATIONS: dict[int, list[str]] = {
    2: [
        "ALTER TABLE blocked_domains ADD COLUMN origem TEXT NOT NULL DEFAULT 'manual'",
        "CREATE INDEX IF NOT EXISTS idx_blocked_origem ON blocked_domains(origem)",
        """CREATE TABLE IF NOT EXISTS liberacoes (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            domain          TEXT    NOT NULL UNIQUE,
            processo        TEXT    NOT NULL,
            data_liberacao  TEXT    NOT NULL DEFAULT (datetime('now')),
            usuario         TEXT    NOT NULL DEFAULT '-',
            notes           TEXT,
            active          INTEGER NOT NULL DEFAULT 1
        )""",
        "CREATE INDEX IF NOT EXISTS idx_liberacoes_active ON liberacoes(active)",
        """CREATE TABLE IF NOT EXISTS config_anablock (
            id                    INTEGER PRIMARY KEY CHECK (id = 1),
            enabled               INTEGER NOT NULL DEFAULT 0,
            token                 TEXT    NOT NULL DEFAULT '',
            api_url               TEXT    NOT NULL DEFAULT 'https://api.anablock.net.br/api/domain/all',
            frequencia            INTEGER NOT NULL DEFAULT 360,
            remover_ausentes      INTEGER NOT NULL DEFAULT 1,
            ultima_sincronizacao  TEXT,
            ultimo_status         TEXT    NOT NULL DEFAULT '',
            total_importados      INTEGER NOT NULL DEFAULT 0
        )""",
        "INSERT OR IGNORE INTO config_anablock (id) VALUES (1)",
    ],
    3: [
        """CREATE TABLE IF NOT EXISTS federacao_publicacao (
            id                    INTEGER PRIMARY KEY CHECK (id = 1),
            publicar              INTEGER NOT NULL DEFAULT 0,
            token_publicacao      TEXT    NOT NULL DEFAULT '',
            publicar_liberacoes   INTEGER NOT NULL DEFAULT 1,
            consumir              INTEGER NOT NULL DEFAULT 0,
            frequencia            INTEGER NOT NULL DEFAULT 360,
            remover_ausentes      INTEGER NOT NULL DEFAULT 0,
            ultima_sincronizacao  TEXT,
            ultimo_status         TEXT    NOT NULL DEFAULT ''
        )""",
        "INSERT OR IGNORE INTO federacao_publicacao (id) VALUES (1)",
        """CREATE TABLE IF NOT EXISTS federacao_peers (
            id                    INTEGER PRIMARY KEY AUTOINCREMENT,
            nome                  TEXT    NOT NULL,
            url                   TEXT    NOT NULL UNIQUE,
            token                 TEXT    NOT NULL DEFAULT '',
            enabled               INTEGER NOT NULL DEFAULT 1,
            importar_liberacoes   INTEGER NOT NULL DEFAULT 1,
            ultima_sincronizacao  TEXT,
            ultimo_status         TEXT    NOT NULL DEFAULT '',
            total_importados      INTEGER NOT NULL DEFAULT 0,
            created_at            TEXT    NOT NULL DEFAULT (datetime('now'))
        )""",
        "CREATE INDEX IF NOT EXISTS idx_peers_enabled ON federacao_peers(enabled)",
    ],
    4: [
        """CREATE TABLE IF NOT EXISTS licenca (
            id                     INTEGER PRIMARY KEY CHECK (id = 1),
            cnpj                   TEXT    NOT NULL DEFAULT '',
            chave                  TEXT    NOT NULL DEFAULT '',
            url                    TEXT    NOT NULL DEFAULT 'https://licenca.jbinfo.com.br',
            status                 TEXT    NOT NULL DEFAULT 'nao_configurado',
            modulos                TEXT    NOT NULL DEFAULT '',
            contrato               TEXT    NOT NULL DEFAULT '',
            validade               TEXT    NOT NULL DEFAULT '',
            mensagem               TEXT    NOT NULL DEFAULT '',
            ultima_verificacao     TEXT,
            ultima_verificacao_ok  TEXT
        )""",
        "INSERT OR IGNORE INTO licenca (id) VALUES (1)",
    ],
}


SCHEMA_FILE = Path(__file__).resolve().parent.parent / "db_schema.sql"

# Colunas que precisam existir mesmo em bancos criados por versões antigas.
REQUIRED_COLUMNS: dict[str, dict[str, str]] = {
    "blocked_domains": {"origem": "TEXT NOT NULL DEFAULT 'manual'", "notes": "TEXT"},
    "liberacoes": {"notes": "TEXT", "usuario": "TEXT NOT NULL DEFAULT '-'"},
}

SINGLETON_ROWS = (
    "provider_settings",
    "config_anablock",
    "federacao_publicacao",
    "licenca",
)

_ALREADY_APPLIED = ("duplicate column", "already exists")


def connect() -> sqlite3.Connection:
    cfg = get_config()
    Path(cfg.db_path).parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(cfg.db_path, timeout=15)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    conn.execute("PRAGMA journal_mode = WAL")
    return conn


def get_db() -> sqlite3.Connection:
    if "db" not in g:
        g.db = connect()
    return g.db


def current_version(conn: sqlite3.Connection) -> int:
    try:
        row = conn.execute("SELECT version FROM schema_version WHERE id = 1").fetchone()
        return int(row["version"]) if row else 0
    except sqlite3.OperationalError:
        return 0


def _exec_tolerant(conn: sqlite3.Connection, stmt: str, logger=None) -> None:
    """Executa uma instrução ignorando apenas erros de 'já aplicada'."""
    try:
        conn.execute(stmt)
    except sqlite3.OperationalError as exc:
        msg = str(exc).lower()
        if any(token in msg for token in _ALREADY_APPLIED):
            return
        if logger:
            logger.warning("Migração ignorada (%s): %s", exc, stmt.strip().split("\n")[0])


def ensure_schema(conn: sqlite3.Connection, logger=None) -> None:
    """Auto-reparo: aplica o schema completo e as colunas faltantes.

    Bancos criados por versões antigas podem ter ficado pela metade quando uma
    migração em bloco abortou na primeira instrução já aplicada. Aqui a
    verificação é feita pelo schema real, não pelo número de versão gravado.
    """
    if SCHEMA_FILE.exists():
        try:
            conn.executescript(SCHEMA_FILE.read_text(encoding="utf-8"))
        except sqlite3.Error as exc:
            if logger:
                logger.warning("Falha ao aplicar db_schema.sql: %s", exc)

    for table, columns in REQUIRED_COLUMNS.items():
        try:
            existing = {r["name"] for r in conn.execute(f"PRAGMA table_info({table})")}
        except sqlite3.Error:
            continue
        if not existing:
            continue
        for name, ddl in columns.items():
            if name not in existing:
                _exec_tolerant(conn, f"ALTER TABLE {table} ADD COLUMN {name} {ddl}", logger)
                if logger:
                    logger.info("Coluna %s.%s criada pelo auto-reparo.", table, name)

    for table in SINGLETON_ROWS:
        _exec_tolerant(conn, f"INSERT OR IGNORE INTO {table} (id) VALUES (1)", logger)
    conn.commit()


def migrate(conn: sqlite3.Connection, logger=None) -> int:
    """Aplica migrações pendentes preservando os dados existentes."""
    version = current_version(conn)
    started = version
    for target in sorted(MIGRATIONS):
        if target > version:
            for stmt in MIGRATIONS[target]:
                _exec_tolerant(conn, stmt, logger)
            version = target

    ensure_schema(conn, logger)

    final = max(version, SCHEMA_VERSION)
    _exec_tolerant(
        conn,
        "CREATE TABLE IF NOT EXISTS schema_version ("
        "id INTEGER PRIMARY KEY CHECK (id = 1), version INTEGER NOT NULL, "
        "updated_at TEXT NOT NULL DEFAULT (datetime('now')))",
        logger,
    )
    conn.execute(
        "INSERT INTO schema_version (id, version, updated_at) "
        "VALUES (1, ?, datetime('now')) "
        "ON CONFLICT(id) DO UPDATE SET version = excluded.version, "
        "updated_at = excluded.updated_at",
        (final,),
    )
    conn.commit()
    if logger:
        logger.info("Schema do banco: versão %s -> %s", started, final)
    return final


def init_app_db(app: Flask) -> None:
    with app.app_context():
        conn = connect()
        try:
            migrate(conn, app.logger)
        except sqlite3.Error as exc:
            app.logger.exception("Falha ao migrar o banco: %s", exc)
        finally:
            conn.close()


def get_provider() -> dict:
    try:
        row = get_db().execute("SELECT * FROM provider_settings WHERE id = 1").fetchone()
    except sqlite3.Error:
        row = None
    if row is None:
        return {
            "trade_name": "Provedor",
            "legal_name": "",
            "cnpj": "",
            "support_email": "",
            "return_url": "https://example.com.br",
        }
    return dict(row)
