#!/usr/bin/env python3
"""
Gera um único ZIP para migrar o OMRCheck-Web para outra máquina.

Exportar (máquina de origem):
  python scripts/pacote_migracao.py exportar

Importar (máquina nova, após configurar .env e init_database.py):
  python scripts/pacote_migracao.py importar --arquivo backups\\omrcheck_migracao_YYYYMMDD_HHMMSS.zip

O arquivo a levar para a outra máquina é o .zip gerado em backups/.
"""

from __future__ import annotations

import argparse
import json
import shutil
import sys
import zipfile
from datetime import datetime
from pathlib import Path

from dotenv import load_dotenv

BASE_DIR = Path(__file__).resolve().parent.parent
if str(BASE_DIR) not in sys.path:
    sys.path.insert(0, str(BASE_DIR))

load_dotenv(BASE_DIR / ".env")

from app.db import storage_usa_banco
from app.settings import (
    APP_STORAGE_BACKEND,
    CAMINHO_JOBS,
    CAMINHO_KANBAN,
    PASTA_BACKUPS,
    PASTA_PROCESSAMENTOS,
    PASTA_RUNTIME,
)
from app.time_utils import agora_local

MANIFESTO_NOME = "manifest.json"
BANCO_JSON_INTERNO = "banco/dados.json"
PASTA_PROCESSAMENTOS_INTERNA = "processamentos"
PASTA_RUNTIME_INTERNA = "runtime"


def _nome_zip_padrao() -> Path:
    timestamp = agora_local().strftime("%Y%m%d_%H%M%S")
    return PASTA_BACKUPS / f"omrcheck_migracao_{timestamp}.zip"


def _contar_arquivos(pasta: Path) -> int:
    if not pasta.exists():
        return 0
    return sum(1 for item in pasta.rglob("*") if item.is_file())


def _adicionar_pasta_no_zip(arquivo_zip: zipfile.ZipFile, pasta_origem: Path, prefixo_interno: str) -> int:
    if not pasta_origem.exists():
        return 0

    total = 0
    for item in pasta_origem.rglob("*"):
        if not item.is_file():
            continue
        destino = f"{prefixo_interno}/{item.relative_to(pasta_origem).as_posix()}"
        arquivo_zip.write(item, destino)
        total += 1
    return total


def exportar_pacote(destino: Path) -> dict:
    destino.parent.mkdir(parents=True, exist_ok=True)

    manifesto = {
        "versao": 1,
        "exportado_em": agora_local().isoformat(),
        "storage_backend": APP_STORAGE_BACKEND,
        "caminhos_origem": {
            "processamentos": str(PASTA_PROCESSAMENTOS),
            "runtime": str(PASTA_RUNTIME),
        },
        "conteudo": {
            "banco_mysql": False,
            "processamentos_arquivos": 0,
            "runtime_arquivos": 0,
        },
    }

    with zipfile.ZipFile(destino, "w", compression=zipfile.ZIP_DEFLATED) as arquivo_zip:
        if storage_usa_banco():
            from scripts.transferir_banco import exportar_dados

            dados = exportar_dados()
            arquivo_zip.writestr(
                BANCO_JSON_INTERNO,
                json.dumps(dados, ensure_ascii=False, indent=2),
            )
            manifesto["conteudo"]["banco_mysql"] = True
            manifesto["conteudo"]["registros_banco"] = {
                tabela: len(linhas)
                for tabela, linhas in dados.get("tabelas", {}).items()
            }
        else:
            manifesto["conteudo"]["banco_mysql"] = False
            manifesto["aviso"] = (
                "Origem em modo file: kanban/jobs foram incluídos de runtime/ se existirem."
            )

        manifesto["conteudo"]["processamentos_arquivos"] = _adicionar_pasta_no_zip(
            arquivo_zip,
            PASTA_PROCESSAMENTOS,
            PASTA_PROCESSAMENTOS_INTERNA,
        )

        runtime_incluidos = 0
        if not storage_usa_banco():
            for arquivo_runtime in (CAMINHO_KANBAN, CAMINHO_JOBS):
                if arquivo_runtime.exists() and arquivo_runtime.is_file():
                    interno = f"{PASTA_RUNTIME_INTERNA}/{arquivo_runtime.name}"
                    arquivo_zip.write(arquivo_runtime, interno)
                    runtime_incluidos += 1
        manifesto["conteudo"]["runtime_arquivos"] = runtime_incluidos

        arquivo_zip.writestr(
            MANIFESTO_NOME,
            json.dumps(manifesto, ensure_ascii=False, indent=2),
        )

    manifesto["arquivo_zip"] = str(destino.resolve())
    return manifesto


def _extrair_zip(origem: Path, pasta_destino: Path) -> None:
    pasta_destino.mkdir(parents=True, exist_ok=True)
    with zipfile.ZipFile(origem, "r") as arquivo_zip:
        arquivo_zip.extractall(pasta_destino)


def importar_pacote(origem: Path, *, substituir_banco: bool = True) -> dict:
    if not origem.exists():
        raise FileNotFoundError(f"Pacote não encontrado: {origem}")

    pasta_temp = PASTA_BACKUPS / f"_import_{agora_local().strftime('%Y%m%d_%H%M%S')}"
    resumo = {
        "arquivo": str(origem.resolve()),
        "banco": {},
        "processamentos_arquivos": 0,
        "runtime_arquivos": 0,
    }

    try:
        _extrair_zip(origem, pasta_temp)

        manifesto_path = pasta_temp / MANIFESTO_NOME
        manifesto = {}
        if manifesto_path.exists():
            manifesto = json.loads(manifesto_path.read_text(encoding="utf-8"))
        resumo["manifesto"] = manifesto

        banco_json = pasta_temp / BANCO_JSON_INTERNO
        if banco_json.exists() and storage_usa_banco():
            from scripts.transferir_banco import importar_dados

            payload = json.loads(banco_json.read_text(encoding="utf-8"))
            resumo["banco"] = importar_dados(payload, substituir=substituir_banco)
        elif banco_json.exists() and not storage_usa_banco():
            resumo["aviso_banco"] = (
                "Pacote contém dados MySQL, mas APP_STORAGE_BACKEND=file na máquina destino."
            )

        origem_processamentos = pasta_temp / PASTA_PROCESSAMENTOS_INTERNA
        if origem_processamentos.exists():
            PASTA_PROCESSAMENTOS.mkdir(parents=True, exist_ok=True)
            for item in origem_processamentos.iterdir():
                destino_item = PASTA_PROCESSAMENTOS / item.name
                if item.is_dir():
                    if destino_item.exists():
                        shutil.rmtree(destino_item, ignore_errors=True)
                    shutil.copytree(item, destino_item)
                    resumo["processamentos_arquivos"] += _contar_arquivos(destino_item)
                elif item.is_file():
                    shutil.copy2(item, destino_item)
                    resumo["processamentos_arquivos"] += 1

        origem_runtime = pasta_temp / PASTA_RUNTIME_INTERNA
        if origem_runtime.exists() and not storage_usa_banco():
            PASTA_RUNTIME.mkdir(parents=True, exist_ok=True)
            for item in origem_runtime.iterdir():
                if item.is_file():
                    shutil.copy2(item, PASTA_RUNTIME / item.name)
                    resumo["runtime_arquivos"] += 1

        return resumo
    finally:
        shutil.rmtree(pasta_temp, ignore_errors=True)


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Exporta/importa pacote completo (banco + processamentos) em um ZIP"
    )
    sub = parser.add_subparsers(dest="comando", required=True)

    exportar = sub.add_parser("exportar", help="Gera o ZIP de migração")
    exportar.add_argument(
        "--arquivo",
        default="",
        help="Caminho do ZIP de saída (padrão: backups/omrcheck_migracao_AAAAMMDD_HHMMSS.zip)",
    )

    importar = sub.add_parser("importar", help="Restaura o ZIP na máquina nova")
    importar.add_argument("--arquivo", required=True, help="ZIP gerado pelo exportar")
    importar.add_argument(
        "--manter-banco-existente",
        action="store_true",
        help="Não apaga registros do MySQL antes de importar",
    )

    args = parser.parse_args()

    if args.comando == "exportar":
        destino = Path(args.arquivo) if args.arquivo else _nome_zip_padrao()
        manifesto = exportar_pacote(destino)
        print(f"[OK] Pacote gerado:")
        print(f"     {destino.resolve()}")
        print("")
        print("Leve ESTE arquivo .zip para a outra máquina.")
        print("")
        if manifesto["conteudo"].get("banco_mysql"):
            registros = manifesto["conteudo"].get("registros_banco", {})
            total = sum(int(v) for v in registros.values())
            print(f"Banco MySQL: {total} registro(s)")
            for tabela, qtd in registros.items():
                print(f"  - {tabela}: {qtd}")
        print(f"Processamentos: {manifesto['conteudo']['processamentos_arquivos']} arquivo(s)")
        if manifesto["conteudo"].get("runtime_arquivos"):
            print(f"Runtime (modo file): {manifesto['conteudo']['runtime_arquivos']} arquivo(s)")
        print("")
        print("Na máquina nova:")
        print("  1. Configure o .env (MYSQL_* e APP_STORAGE_BACKEND=mysql)")
        print("  2. python scripts/init_database.py --criar-banco")
        print(f"  3. python scripts/pacote_migracao.py importar --arquivo \"{destino.name}\"")
        return 0

    origem = Path(args.arquivo)
    if not origem.is_absolute():
        candidatos = [origem, PASTA_BACKUPS / origem.name, BASE_DIR / origem]
        origem = next((c for c in candidatos if c.exists()), origem)

    try:
        resumo = importar_pacote(origem, substituir_banco=not args.manter_banco_existente)
    except Exception as erro:
        print(f"[ERRO] {erro}")
        return 1

    print(f"[OK] Pacote importado de:")
    print(f"     {resumo['arquivo']}")
    if resumo.get("banco"):
        print("Banco MySQL:")
        for tabela, qtd in resumo["banco"].items():
            print(f"  - {tabela}: {qtd}")
    if resumo.get("aviso_banco"):
        print(f"[AVISO] {resumo['aviso_banco']}")
    print(f"Processamentos restaurados: {resumo['processamentos_arquivos']} arquivo(s)")
    if resumo.get("runtime_arquivos"):
        print(f"Runtime restaurado: {resumo['runtime_arquivos']} arquivo(s)")
    return 0


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