from __future__ import annotations

import json
import re
from pathlib import Path

from .db import MysqlCli, sql_blob_hex, sql_quote
from .security import (
    CERTIFICATE_AAD_KEY_ID_SIAFI_V1,
    certificate_aad_siafi,
    decrypt_certificate_value,
    encrypt_blob,
    encrypt_bytes,
    sha256_hex,
)
from .certificates import CertificateInfo, validate_pfx


ROOT = Path(__file__).resolve().parents[2]
TENANT_TEMPLATE = ROOT / "migrations" / "templates" / "create_tenant_tables.sql"


def validate_codigo_ibge(codigo_ibge: str | None) -> str | None:
    codigo = (codigo_ibge or "").strip()
    if not codigo:
        return None
    if not re.fullmatch(r"\d{7}", codigo):
        raise ValueError("Codigo IBGE deve ter exatamente 7 digitos numericos quando informado.")
    return codigo


def validate_codigo_siafi(codigo_siafi: str) -> str:
    codigo = codigo_siafi.strip()
    if not re.fullmatch(r"\d{1,10}", codigo):
        raise ValueError("Codigo SIAFI deve conter apenas numeros, com ate 10 digitos.")
    return codigo


def tenant_table_names(codigo_siafi: str, ambiente: str, perfil: str) -> dict[str, str]:
    codigo = validate_codigo_siafi(codigo_siafi)
    if ambiente not in ("producao_restrita", "producao"):
        raise ValueError("Ambiente invalido.")
    if perfil not in ("municipio", "contribuinte"):
        raise ValueError("Perfil invalido.")
    suffix = f"{codigo}_{ambiente}_{perfil}"
    return {
        "documentos": f"nfse_documentos_{suffix}",
        "eventos": f"nfse_eventos_{suffix}",
        "partes": f"nfse_documento_partes_{suffix}",
    }


def ensure_tenant_tables(db: MysqlCli, codigo_siafi: str, ambiente: str, perfil: str) -> None:
    codigo = validate_codigo_siafi(codigo_siafi)
    tenant_table_names(codigo, ambiente, perfil)
    suffix = f"{codigo}_{ambiente}_{perfil}"
    sql = (
        TENANT_TEMPLATE.read_text(encoding="utf-8")
        .replace("__CODIGO_TENANT__", suffix)
        .replace("__CODIGO_SIAFI__", codigo)
        .replace("__AMBIENTE__", ambiente)
        .replace("__PERFIL__", perfil)
    )
    db.execute_file_sql(sql)


def list_municipios(db: MysqlCli) -> list[dict[str, str]]:
    rows = db.query_rows(
        """
SELECT
  m.id,
  m.codigo_ibge,
  m.codigo_siafi,
  m.nome,
  m.uf,
  m.ambiente,
  m.ativo,
  COALESCE(s.last_nsu, 0),
  COALESCE(s.status, 'sem_estado'),
  COALESCE(DATE_FORMAT(s.last_success_at, '%d/%m/%Y %H:%i'), ''),
  COALESCE(DATE_FORMAT(c.valido_ate, '%d/%m/%Y %H:%i'), ''),
  CASE WHEN c.valido_ate IS NOT NULL AND c.valido_ate < NOW() THEN 1 ELSE 0 END,
  COALESCE(c.subject_name, '')
FROM nfse_municipios m
LEFT JOIN nfse_sync_state s ON s.municipio_id = m.id
LEFT JOIN nfse_certificados c ON c.municipio_id = m.id AND c.ativo = 1
ORDER BY m.codigo_siafi
"""
    )
    keys = [
        "id",
        "codigo_ibge",
        "codigo_siafi",
        "nome",
        "uf",
        "ambiente",
        "ativo",
        "last_nsu",
        "status",
        "last_success_at",
        "valido_ate",
        "certificado_expirado",
        "subject_name",
    ]
    return [dict(zip(keys, row)) for row in rows]


def get_municipio_certificado(db: MysqlCli, municipio_id: int) -> dict[str, str]:
    rows = db.query_rows(
        f"""
SELECT
  m.id,
  m.codigo_ibge,
  m.codigo_siafi,
  m.nome,
  m.uf,
  m.ambiente,
  c.id,
  c.certificado_criptografado,
  c.certificado_key_id,
  c.senha_key_id,
  c.certificado_nome_original,
  COALESCE(DATE_FORMAT(c.valido_ate, '%d/%m/%Y %H:%i'), ''),
  CASE WHEN c.valido_ate IS NOT NULL AND c.valido_ate < NOW() THEN 1 ELSE 0 END,
  COALESCE(c.documento_federal, ''),
  COALESCE(c.subject_name, '')
FROM nfse_municipios m
LEFT JOIN nfse_certificados c ON c.municipio_id = m.id AND c.ativo = 1
WHERE m.id = {municipio_id}
LIMIT 1;
"""
    )
    if not rows:
        raise ValueError("Município não encontrado.")
    keys = [
        "id",
        "codigo_ibge",
        "codigo_siafi",
        "nome",
        "uf",
        "ambiente",
        "certificado_id",
        "certificado_criptografado",
        "certificado_key_id",
        "senha_key_id",
        "certificado_nome_original",
        "valido_ate",
        "certificado_expirado",
        "documento_federal",
        "subject_name",
    ]
    return dict(zip(keys, rows[0]))


def _upsert_active_certificate(
    db: MysqlCli,
    *,
    municipio_id: int,
    encrypted_pfx: bytes,
    encrypted_password: str,
    pfx_hash: str,
    original_filename: str,
    cert_info: CertificateInfo,
) -> None:
    db.execute(
        f"""
UPDATE nfse_certificados
SET ativo = 0
WHERE municipio_id = {municipio_id};
"""
    )
    existing = db.query_rows(
        f"""
SELECT id
FROM nfse_certificados
WHERE municipio_id = {municipio_id}
  AND certificado_sha256 = {sql_quote(pfx_hash)}
LIMIT 1
FOR UPDATE;
"""
    )

    if existing:
        db.execute(
            f"""
UPDATE nfse_certificados
SET storage_tipo = 'db_encrypted',
    arquivo_path = NULL,
    certificado_criptografado = {sql_blob_hex(encrypted_pfx)},
    certificado_crypto_alg = 'AES-256-GCM',
    certificado_key_id = {sql_quote(CERTIFICATE_AAD_KEY_ID_SIAFI_V1)},
    certificado_nome_original = {sql_quote(original_filename)},
    senha_criptografada = {sql_quote(encrypted_password)},
    senha_crypto_alg = 'AES-256-GCM',
    senha_key_id = {sql_quote(CERTIFICATE_AAD_KEY_ID_SIAFI_V1)},
    subject_name = {sql_quote(cert_info.subject_name)},
    documento_federal = {sql_quote(cert_info.documento_federal)},
    valido_de = {sql_quote(cert_info.valido_de)},
    valido_ate = {sql_quote(cert_info.valido_ate)},
    ativo = 1
WHERE id = {int(existing[0][0])};
"""
        )
        return

    db.execute(
        f"""
INSERT INTO nfse_certificados (
  municipio_id, tipo, storage_tipo, arquivo_path, certificado_criptografado,
  certificado_crypto_alg, certificado_key_id, certificado_sha256, certificado_nome_original,
  senha_criptografada, senha_crypto_alg, senha_key_id, subject_name, documento_federal,
  valido_de, valido_ate, ativo
) VALUES (
  {municipio_id}, 'A1_PFX', 'db_encrypted', NULL, {sql_blob_hex(encrypted_pfx)},
  'AES-256-GCM', {sql_quote(CERTIFICATE_AAD_KEY_ID_SIAFI_V1)}, {sql_quote(pfx_hash)}, {sql_quote(original_filename)},
  {sql_quote(encrypted_password)}, 'AES-256-GCM', {sql_quote(CERTIFICATE_AAD_KEY_ID_SIAFI_V1)},
  {sql_quote(cert_info.subject_name)}, {sql_quote(cert_info.documento_federal)},
  {sql_quote(cert_info.valido_de)}, {sql_quote(cert_info.valido_ate)}, 1
);
"""
    )


def replace_municipio_certificate(
    db: MysqlCli,
    *,
    municipio_id: int,
    pfx_data: bytes,
    pfx_password: str,
    original_filename: str,
    cert_info: CertificateInfo,
) -> None:
    municipio = get_municipio_certificado(db, municipio_id)
    siafi = validate_codigo_siafi(municipio["codigo_siafi"])
    aad = certificate_aad_siafi(siafi)
    encrypted_pfx = encrypt_blob(pfx_data, aad=aad)
    encrypted_password = encrypt_bytes(pfx_password.encode("utf-8"), aad=aad)
    pfx_hash = sha256_hex(pfx_data)

    with db.transaction():
        _upsert_active_certificate(
            db,
            municipio_id=municipio_id,
            encrypted_pfx=encrypted_pfx,
            encrypted_password=encrypted_password,
            pfx_hash=pfx_hash,
            original_filename=original_filename,
            cert_info=cert_info,
        )
        db.execute(
            f"""
INSERT INTO nfse_sync_logs (municipio_id, ambiente, nivel, evento, mensagem)
VALUES ({municipio_id}, {sql_quote(municipio['ambiente'])}, 'info', 'certificado.trocado', 'Certificado trocado pelo painel web');
"""
        )


def update_municipio_fields(
    db: MysqlCli,
    *,
    municipio_id: int,
    nome: str,
    uf: str,
    codigo_ibge: str | None = None,
) -> None:
    nome_s = nome.strip()
    uf_s = uf.strip().upper()
    codigo = validate_codigo_ibge(codigo_ibge)
    if not nome_s:
        raise ValueError("Nome do municipio e obrigatorio.")
    if not re.fullmatch(r"[A-Z]{2}", uf_s):
        raise ValueError("UF invalida.")

    with db.transaction():
        municipio = get_municipio_certificado(db, municipio_id)
        db.execute(
            f"""
UPDATE nfse_municipios
SET nome = {sql_quote(nome_s)},
    uf = {sql_quote(uf_s)},
    codigo_ibge = {sql_quote(codigo)}
WHERE id = {municipio_id};

INSERT INTO nfse_sync_logs (municipio_id, ambiente, nivel, evento, mensagem)
VALUES ({municipio_id}, {sql_quote(municipio['ambiente'])}, 'info', 'municipio.editado', 'Municipio editado pelo painel web');
"""
        )


def update_municipio_certificate_password(
    db: MysqlCli,
    *,
    municipio_id: int,
    pfx_password: str,
) -> None:
    municipio = get_municipio_certificado(db, municipio_id)
    if not municipio.get("certificado_id"):
        raise ValueError("Municipio nao possui certificado ativo.")
    siafi = validate_codigo_siafi(municipio["codigo_siafi"])
    aad = certificate_aad_siafi(siafi)
    pfx_data = decrypt_certificate_value(
        municipio["certificado_criptografado"],
        codigo_siafi=siafi,
        key_id=municipio.get("certificado_key_id"),
    )
    validate_pfx(pfx_data, pfx_password)
    encrypted_pfx = encrypt_blob(pfx_data, aad=aad)
    encrypted_password = encrypt_bytes(pfx_password.encode("utf-8"), aad=aad)
    certificado_id = int(municipio["certificado_id"])

    with db.transaction():
        active_rows = db.query_rows(
            f"""
SELECT id
FROM nfse_certificados
WHERE municipio_id = {municipio_id}
  AND ativo = 1
FOR UPDATE;
"""
        )
        if not active_rows or int(active_rows[0][0]) != certificado_id:
            raise RuntimeError("O certificado ativo mudou durante a alteracao; tente novamente.")

        db.execute(
            f"""
UPDATE nfse_certificados
SET certificado_criptografado = {sql_blob_hex(encrypted_pfx)},
    certificado_key_id = {sql_quote(CERTIFICATE_AAD_KEY_ID_SIAFI_V1)},
    senha_criptografada = {sql_quote(encrypted_password)},
    senha_crypto_alg = 'AES-256-GCM',
    senha_key_id = {sql_quote(CERTIFICATE_AAD_KEY_ID_SIAFI_V1)}
WHERE id = {certificado_id}
  AND ativo = 1;

INSERT INTO nfse_sync_logs (municipio_id, ambiente, nivel, evento, mensagem)
VALUES ({municipio_id}, {sql_quote(municipio['ambiente'])}, 'info', 'certificado.senha_alterada', 'Senha do certificado alterada pelo painel web');
"""
        )


def upsert_municipio_with_certificate(
    db: MysqlCli,
    *,
    codigo_ibge: str | None,
    codigo_siafi: str,
    nome: str,
    uf: str,
    ambiente: str,
    pfx_data: bytes,
    pfx_password: str,
    original_filename: str,
    cert_info: CertificateInfo,
) -> int:
    codigo = validate_codigo_ibge(codigo_ibge)
    siafi = validate_codigo_siafi(codigo_siafi)
    names = tenant_table_names(siafi, ambiente, "municipio")
    ensure_tenant_tables(db, siafi, ambiente, "municipio")

    aad = certificate_aad_siafi(siafi)
    encrypted_pfx = encrypt_blob(pfx_data, aad=aad)
    encrypted_password = encrypt_bytes(pfx_password.encode("utf-8"), aad=aad)
    pfx_hash = sha256_hex(pfx_data)
    tenant_schema = db.database

    with db.transaction():
        db.execute(
            f"""
INSERT INTO nfse_municipios (
  codigo_ibge, codigo_siafi, nome, uf, tenant_schema, tabela_documentos, tabela_eventos, tabela_partes,
  ambiente, perfil, ativo
) VALUES (
  {sql_quote(codigo)}, {sql_quote(siafi)}, {sql_quote(nome)}, {sql_quote(uf.upper())}, {sql_quote(tenant_schema)},
  {sql_quote(names['documentos'])}, {sql_quote(names['eventos'])}, {sql_quote(names['partes'])},
  {sql_quote(ambiente)}, 'municipio', 1
)
ON DUPLICATE KEY UPDATE
  nome = VALUES(nome),
  codigo_siafi = VALUES(codigo_siafi),
  uf = VALUES(uf),
  tenant_schema = VALUES(tenant_schema),
  tabela_documentos = VALUES(tabela_documentos),
  tabela_eventos = VALUES(tabela_eventos),
  tabela_partes = VALUES(tabela_partes),
  ativo = 1;
"""
        )
        row = db.query_rows(
            "SELECT id FROM nfse_municipios "
            f"WHERE codigo_siafi={sql_quote(siafi)} AND ambiente={sql_quote(ambiente)} "
            "AND perfil='municipio' LIMIT 1 FOR UPDATE;"
        )
        if not row:
            raise RuntimeError("Municipio nao foi localizado apos cadastro.")
        municipio_id = int(row[0][0])

        _upsert_active_certificate(
            db,
            municipio_id=municipio_id,
            encrypted_pfx=encrypted_pfx,
            encrypted_password=encrypted_password,
            pfx_hash=pfx_hash,
            original_filename=original_filename,
            cert_info=cert_info,
        )
        db.execute(
            f"""
INSERT INTO nfse_sync_state (municipio_id, ambiente, perfil, last_nsu, status)
VALUES ({municipio_id}, {sql_quote(ambiente)}, 'municipio', 0, 'idle')
ON DUPLICATE KEY UPDATE status = 'idle';

INSERT INTO nfse_sync_logs (municipio_id, ambiente, nivel, evento, mensagem, contexto)
VALUES (
  {municipio_id},
  {sql_quote(ambiente)},
  'info',
  'municipio.cadastrado',
  'Municipio cadastrado pelo painel web',
  {sql_quote(json.dumps({"codigo_siafi": siafi, "codigo_ibge": codigo}, ensure_ascii=False))}
);
"""
        )

    return municipio_id
