"""Diagnóstico do sistema, instalação automática e download de modelos."""

from __future__ import annotations

import contextlib
import io
import logging
import os
import platform
import shutil
import subprocess
import sys
import warnings
from dataclasses import dataclass

os.environ["PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK"] = "True"

# Suprime warnings ruidosos do paddle/requests durante diagnóstico
warnings.filterwarnings("ignore", category=UserWarning, module="paddle")
warnings.filterwarnings("ignore", category=DeprecationWarning, module="requests")
for _name in ("paddle", "paddlex", "paddleocr", "ppocr"):
    logging.getLogger(_name).setLevel(logging.ERROR)

PADDLE_GPU_INDEX = "https://www.paddlepaddle.org.cn/packages/stable/cu118/"
PADDLE_GPU_PKG = "paddlepaddle-gpu==3.2.0"
PADDLE_CPU_PKG = "paddlepaddle==3.2.0"


@dataclass
class CheckResult:
    """Resultado de uma verificação."""

    name: str
    ok: bool
    detail: str
    required: bool = True
    auto_fixable: bool = False


def check_python() -> CheckResult:
    """Verifica versão do Python."""
    ver = sys.version_info
    ok = ver >= (3, 10)
    return CheckResult(
        name="Python",
        ok=ok,
        detail=f"{ver.major}.{ver.minor}.{ver.micro}" + ("" if ok else " (requer >= 3.10)"),
    )


def check_platform() -> CheckResult:
    """Info do sistema operacional."""
    info = f"{platform.system()} {platform.machine()}"
    return CheckResult(name="Sistema", ok=True, detail=info)


def check_venv() -> CheckResult:
    """Verifica se está rodando dentro de um ambiente virtual."""
    in_venv = sys.prefix != sys.base_prefix
    if in_venv:
        return CheckResult(name="Ambiente virtual", ok=True, detail=sys.prefix)
    return CheckResult(
        name="Ambiente virtual",
        ok=False,
        detail="NÃO — recomendado usar venv para evitar conflitos",
        required=False,
    )


def has_nvidia_gpu() -> bool:
    """Detecta se há GPU NVIDIA no sistema via nvidia-smi."""
    try:
        result = subprocess.run(
            ["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        return result.returncode == 0 and len(result.stdout.strip()) > 0
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False


def get_nvidia_gpu_name() -> str:
    """Retorna nome da GPU NVIDIA."""
    try:
        result = subprocess.run(
            ["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        if result.returncode == 0:
            return result.stdout.strip().split("\n")[0]
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass
    return "desconhecida"


def check_nvidia() -> CheckResult:
    """Verifica se há GPU NVIDIA no sistema (antes de instalar PaddlePaddle)."""
    if has_nvidia_gpu():
        name = get_nvidia_gpu_name()
        return CheckResult(name="GPU NVIDIA", ok=True, detail=name)
    return CheckResult(
        name="GPU NVIDIA",
        ok=False,
        detail="não detectada (nvidia-smi) — usará CPU",
        required=False,
    )


def check_paddle() -> CheckResult:
    """Verifica se PaddlePaddle está instalado."""
    try:
        import paddle

        is_gpu = paddle.device.is_compiled_with_cuda()
        variant = "GPU" if is_gpu else "CPU"
        return CheckResult(
            name="PaddlePaddle", ok=True, detail=f"v{paddle.__version__} ({variant})"
        )
    except ImportError:
        return CheckResult(
            name="PaddlePaddle",
            ok=False,
            detail="NÃO instalado",
            auto_fixable=True,
        )


def check_cuda() -> CheckResult:
    """Verifica se PaddlePaddle GPU funciona com CUDA."""
    try:
        import paddle

        has_cuda = paddle.device.is_compiled_with_cuda()  # paddle já importado, sem print
        if has_cuda:
            count = paddle.device.cuda.device_count()
            if count > 0:
                try:
                    props = paddle.device.cuda.get_device_properties(0)
                    gpu_name = props.name
                except Exception:
                    gpu_name = "desconhecida"
                return CheckResult(
                    name="CUDA ativo",
                    ok=True,
                    detail=f"{count} GPU(s) — {gpu_name}",
                )
            return CheckResult(
                name="CUDA ativo",
                ok=False,
                detail="CUDA compilado mas nenhuma GPU encontrada",
                required=False,
            )
        return CheckResult(
            name="CUDA ativo",
            ok=False,
            detail="PaddlePaddle compilado sem CUDA (versão CPU)",
            required=False,
        )
    except ImportError:
        return CheckResult(
            name="CUDA ativo", ok=False, detail="PaddlePaddle não instalado", required=False
        )


def check_paddleocr() -> CheckResult:
    """Verifica se PaddleOCR está instalado."""
    try:
        import paddleocr

        ver = getattr(paddleocr, "__version__", "desconhecida")
        return CheckResult(name="PaddleOCR", ok=True, detail=f"v{ver}")
    except ImportError:
        return CheckResult(
            name="PaddleOCR",
            ok=False,
            detail="NÃO instalado",
            auto_fixable=True,
        )


def check_pypdfium2() -> CheckResult:
    """Verifica se pypdfium2 está instalado."""
    try:
        import pypdfium2

        ver = getattr(pypdfium2, "__version__", getattr(pypdfium2, "V_PYPDFIUM2", "?"))
        return CheckResult(name="pypdfium2", ok=True, detail=f"v{ver}")
    except ImportError:
        return CheckResult(
            name="pypdfium2",
            ok=False,
            detail="NÃO instalado",
            auto_fixable=True,
        )


def check_disk_space() -> CheckResult:
    """Verifica espaço em disco disponível."""
    home = os.path.expanduser("~")
    usage = shutil.disk_usage(home)
    free_gb = usage.free / (1024**3)
    ok = free_gb >= 2.0
    return CheckResult(
        name="Espaço em disco",
        ok=ok,
        detail=f"{free_gb:.1f} GB livres" + ("" if ok else " (recomendado >= 2 GB)"),
    )


def check_ram() -> CheckResult:
    """Verifica RAM disponível."""
    try:
        import psutil

        mem = psutil.virtual_memory()
        total_gb = mem.total / (1024**3)
        ok = total_gb >= 4.0
        return CheckResult(
            name="RAM",
            ok=ok,
            detail=f"{total_gb:.1f} GB total" + ("" if ok else " (recomendado >= 4 GB)"),
        )
    except ImportError:
        try:
            with open("/proc/meminfo") as f:
                for line in f:
                    if line.startswith("MemTotal:"):
                        kb = int(line.split()[1])
                        total_gb = kb / (1024**2)
                        ok = total_gb >= 4.0
                        return CheckResult(
                            name="RAM",
                            ok=ok,
                            detail=f"{total_gb:.1f} GB total"
                            + ("" if ok else " (recomendado >= 4 GB)"),
                        )
        except Exception:
            pass
        return CheckResult(name="RAM", ok=True, detail="não foi possível verificar")


def _model_cached(model_name: str) -> bool:
    """Verifica se um modelo já está em cache local."""
    cache_dir = os.path.expanduser(f"~/.paddlex/official_models/{model_name}")
    return os.path.isdir(cache_dir)


def check_models() -> list[CheckResult]:
    """Verifica quais modelos OCR estão em cache."""
    models = [
        "PP-OCRv5_mobile_det",
        "PP-OCRv5_mobile_rec",
        "PP-OCRv5_server_det",
        "PP-OCRv5_server_rec",
    ]
    results = []
    for m in models:
        cached = _model_cached(m)
        results.append(
            CheckResult(
                name=f"Modelo {m}",
                ok=cached,
                detail="em cache" if cached else "não baixado",
                required=False,
                auto_fixable=not cached,
            )
        )
    return results


def _has_pip() -> bool:
    """Verifica se pip está disponível no ambiente."""
    result = subprocess.run(
        [sys.executable, "-m", "pip", "--version"],
        capture_output=True, text=True,
    )
    return result.returncode == 0


def _has_uv() -> bool:
    """Verifica se uv está disponível no PATH."""
    return shutil.which("uv") is not None


def pip_install(packages: list[str], index_url: str | None = None) -> bool:
    """Instala pacotes no ambiente atual via pip ou uv.

    Tenta pip primeiro; se não existir (ex: venv do uv tool), usa uv pip.

    Returns:
        True se instalou com sucesso.
    """
    if _has_pip():
        cmd = [sys.executable, "-m", "pip", "install"] + packages
        if index_url:
            cmd.extend(["-i", index_url])
    elif _has_uv():
        cmd = ["uv", "pip", "install", "--python", sys.executable] + packages
        if index_url:
            cmd.extend(["--index-url", index_url])
    else:
        # Último recurso: tenta pip direto
        cmd = [sys.executable, "-m", "pip", "install"] + packages
        if index_url:
            cmd.extend(["-i", index_url])

    result = subprocess.run(cmd, capture_output=False, text=True)
    return result.returncode == 0


def install_paddle(use_gpu: bool) -> bool:
    """Instala PaddlePaddle (GPU ou CPU)."""
    if use_gpu:
        return pip_install([PADDLE_GPU_PKG], index_url=PADDLE_GPU_INDEX)
    return pip_install([PADDLE_CPU_PKG])


def install_paddleocr() -> bool:
    """Instala PaddleOCR com todas as dependências."""
    return pip_install(["paddleocr[all]>=3.4.0"])


def install_pypdfium2() -> bool:
    """Instala pypdfium2."""
    return pip_install(["pypdfium2>=4.0"])


def download_models(models: list[str] | None = None, device: str = "cpu") -> list[str]:
    """Baixa modelos criando uma instância PaddleOCR temporária.

    Returns:
        Lista de modelos baixados com sucesso.
    """
    from paddleocr import PaddleOCR

    if models is None:
        models = ["mobile", "server"]

    downloaded = []
    for model_type in models:
        try:
            PaddleOCR(
                text_detection_model_name=f"PP-OCRv5_{model_type}_det",
                text_recognition_model_name=f"PP-OCRv5_{model_type}_rec",
                use_doc_orientation_classify=False,
                use_doc_unwarping=False,
                use_textline_orientation=False,
                device=device,
            )
            downloaded.append(model_type)
        except Exception as e:
            raise RuntimeError(f"Falha ao baixar modelo {model_type}: {e}") from e

    return downloaded


@contextlib.contextmanager
def _suppress_native_output():
    """Suprime toda saída nativa (C/Python) redirecionando file descriptors."""
    devnull = os.open(os.devnull, os.O_WRONLY)
    old_stdout_fd = os.dup(1)
    old_stderr_fd = os.dup(2)
    os.dup2(devnull, 1)
    os.dup2(devnull, 2)
    # Também redireciona Python-level
    old_stdout = sys.stdout
    old_stderr = sys.stderr
    sys.stdout = io.StringIO()
    sys.stderr = io.StringIO()
    try:
        yield
    finally:
        sys.stdout = old_stdout
        sys.stderr = old_stderr
        os.dup2(old_stdout_fd, 1)
        os.dup2(old_stderr_fd, 2)
        os.close(old_stdout_fd)
        os.close(old_stderr_fd)
        os.close(devnull)


def run_all_checks() -> list[CheckResult]:
    """Executa todas as verificações do sistema."""
    # Checks que não importam paddle (sem ruído)
    checks = [
        check_python(),
        check_platform(),
        check_venv(),
        check_ram(),
        check_disk_space(),
        check_nvidia(),
    ]

    # Checks que importam paddle/paddleocr (com ruído suprimido)
    with _suppress_native_output():
        checks.append(check_paddle())
        checks.append(check_paddleocr())
        checks.append(check_pypdfium2())

        # Só checa CUDA e modelos se PaddlePaddle está instalado
        paddle_ok = any(c.name == "PaddlePaddle" and c.ok for c in checks)
        if paddle_ok:
            checks.append(check_cuda())
            checks.extend(check_models())

    return checks
