"""Exporta lotes OMR do MySQL para os JSONs usados pelo backend ``file``.

Uso seguro (primeiro sem escrita):
  python scripts/exportar_lotes_mysql_para_file.py --school develop

Para gravar e substituir JSONs já existentes:
  python scripts/exportar_lotes_mysql_para_file.py --school develop --apply --overwrite

O banco atual não possui uma coluna de school nos lotes. Por isso este comando
exporta todos os lotes da base configurada para a pasta da school informada.
Execute-o somente na base que contém os dados dessa school, ou use --lote para
exportar um lote específico.
"""
from __future__ import annotations

import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any

from dotenv import load_dotenv


BACKEND_DIR = Path(__file__).resolve().parent.parent
if str(BACKEND_DIR) not in sys.path:
    sys.path.insert(0, str(BACKEND_DIR))
load_dotenv(BACKEND_DIR / ".env")

from app.db import get_engine  # noqa: E402
from app.db_models import LoteLeituraModel, LoteProcessamentoModel  # noqa: E402
from app.lote_leitura import hidratar_dados_leitura  # noqa: E402
from app.lotes_store import nome_arquivo_lote  # noqa: E402
from app.settings import MYSQL_DATABASE, PASTA_PROCESSAMENTOS, nome_pasta_processamentos_para_escola  # noqa: E402


def _dados_json(valor: Any) -> dict[str, Any]:
    return dict(valor) if isinstance(valor, dict) else {}


def _destino(school: str) -> Path:
    return PASTA_PROCESSAMENTOS / nome_pasta_processamentos_para_escola(school)


def _gravar_json(caminho: Path, dados: dict[str, Any]) -> None:
    temporario = caminho.with_suffix(".json.tmp")
    with open(temporario, "w", encoding="utf-8") as arquivo:
        json.dump(dados, arquivo, ensure_ascii=False, indent=2)
    os.replace(temporario, caminho)


def exportar(*, school: str, aplicar: bool, sobrescrever: bool, nomes: set[str]) -> tuple[int, int]:
    pasta = _destino(school)
    engine = get_engine(MYSQL_DATABASE)
    exportados = 0
    ignorados = 0

    # Esta sessão é criada diretamente do engine para que a exportação funcione
    # mesmo quando MYSQL_ENABLED=false no destino, que é justamente o cenário
    # de migração para armazenamento em arquivos.
    from sqlalchemy.orm import sessionmaker

    fabrica_sessao = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)
    with fabrica_sessao() as session:
        consulta = session.query(LoteProcessamentoModel).order_by(LoteProcessamentoModel.criado_em.asc())
        if nomes:
            consulta = consulta.filter(LoteProcessamentoModel.nome_processamento.in_(nomes))

        # Materializa os lotes antes das consultas de leituras. ``yield_per``
        # usa resultado não-bufferizado no PyMySQL; consultar as leituras no
        # meio desse cursor podia interromper a iteração e omitir lotes.
        for lote in consulta.all():
            nome = str(lote.nome_processamento or "").strip()
            if not nome:
                ignorados += 1
                continue

            metadados = _dados_json(lote.metadados)
            metadados["id_processamento"] = nome
            pasta_lote = pasta / nome
            caminho = pasta_lote / "lote.json"
            caminho_legado = pasta / nome_arquivo_lote(nome, metadados)
            if (caminho.exists() or caminho_legado.exists()) and not sobrescrever:
                print(f"[IGNORADO] Já existe: {caminho.name}")
                ignorados += 1
                continue

            # A migration 007 guarda os cartões em linhas próprias. Mantemos a
            # coluna legada como fallback para bases ainda não migradas.
            leituras = {
                str(chave): hidratar_dados_leitura(dados)
                for chave, dados in _dados_json(lote.leituras).items()
            }
            for linha in session.query(LoteLeituraModel).filter_by(lote_id=lote.id):
                leituras[str(linha.chave)] = hidratar_dados_leitura(linha.dados)

            dados_arquivo = {
                "metadados": metadados,
                "leituras": leituras,
                "estado": _dados_json(lote.estado),
            }
            if aplicar:
                pasta_lote.mkdir(parents=True, exist_ok=True)
                _gravar_json(caminho, dados_arquivo)
                if caminho_legado.exists():
                    caminho_legado.unlink()
                print(f"[OK] {caminho.name} ({len(leituras)} leitura(s))")
            else:
                print(f"[SIMULAÇÃO] {caminho.name} ({len(leituras)} leitura(s))")
            exportados += 1

    return exportados, ignorados


def main() -> int:
    parser = argparse.ArgumentParser(description="Exporta lotes OMR do MySQL para arquivos JSON.")
    parser.add_argument("--school", required=True, help="School cujo diretório processamentos receberá os JSONs.")
    parser.add_argument("--lote", action="append", default=[], help="Nome de um lote específico; pode ser repetido.")
    parser.add_argument("--apply", action="store_true", help="Grava os arquivos. Sem esta opção, apenas simula.")
    parser.add_argument("--overwrite", action="store_true", help="Substitui JSONs de destino já existentes (requer --apply).")
    args = parser.parse_args()

    if args.overwrite and not args.apply:
        parser.error("--overwrite requer --apply")

    school = str(args.school or "").strip().lower()
    if not school:
        parser.error("--school não pode ser vazio")

    try:
        exportados, ignorados = exportar(
            school=school,
            aplicar=args.apply,
            sobrescrever=args.overwrite,
            nomes={str(nome).strip() for nome in args.lote if str(nome).strip()},
        )
    except Exception as erro:
        print(f"[ERRO] Falha ao exportar da base `{MYSQL_DATABASE}`: {erro}")
        return 1

    acao = "exportado(s)" if args.apply else "encontrado(s) na simulação"
    print(f"Resumo: {exportados} lote(s) {acao}; {ignorados} ignorado(s).")
    if not args.apply:
        print("Nenhum arquivo foi alterado. Revise a lista e execute novamente com --apply.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
