from __future__ import annotations

from typer.testing import CliRunner

from ocr_pipeline.cli import app
from ocr_pipeline.config import settings
from ocr_pipeline.doctor import (
    check_config,
    check_db,
    check_output_dir,
    check_paddle,
    check_python,
    check_spacy,
    check_tables,
    check_tesseract,
    check_torch,
    exit_code_for,
    render,
    run_checks,
)

runner = CliRunner()


def test_check_python_ok() -> None:
    r = check_python()
    assert r.status == "ok"
    assert "Python" in r.name


def test_check_tesseract_ok_when_on_path(monkeypatch) -> None:
    monkeypatch.setattr("shutil.which", lambda _: "/usr/bin/tesseract")
    r = check_tesseract()
    assert r.status == "ok"
    assert "/usr/bin/tesseract" in r.detail


def test_check_tesseract_fails_when_missing(monkeypatch) -> None:
    monkeypatch.setattr("shutil.which", lambda _: None)
    r = check_tesseract()
    assert r.status == "fail"
    assert r.hint is not None


def test_check_paddle_warns_when_missing(monkeypatch) -> None:
    monkeypatch.setattr(
        "ocr_pipeline.doctor._has_module", lambda name: False if name == "paddleocr" else True
    )
    r = check_paddle()
    assert r.status == "warn"
    assert "paddle" in r.hint.lower() if r.hint else True


def test_check_torch_reports_backend(monkeypatch) -> None:
    monkeypatch.setattr("ocr_pipeline.doctor._has_module", lambda name: True)
    fake_torch = type(
        "T", (), {"cuda": type("C", (), {"is_available": staticmethod(lambda: False)})()}
    )
    monkeypatch.setattr("sys.modules", {"torch": fake_torch})
    r = check_torch()
    assert r.status == "ok"
    assert "backend=" in r.detail


def test_check_tables_warns_when_missing(monkeypatch) -> None:
    monkeypatch.setattr(
        "ocr_pipeline.doctor._has_module",
        lambda name: False if name in ("torch", "transformers") else True,
    )
    r = check_tables()
    assert r.status == "warn"


def test_check_spacy_warns_when_model_missing(monkeypatch, tmp_path) -> None:
    monkeypatch.setattr("ocr_pipeline.doctor._has_module", lambda name: True)

    class FakeSpacy:
        @staticmethod
        def load(_model):
            raise OSError("model not found")

    monkeypatch.setitem(__import__("sys").modules, "spacy", FakeSpacy)
    r = check_spacy()
    assert r.status == "warn"
    assert "en_core_sci_lg" in (r.hint or "")


def test_check_config_warns_when_no_file(monkeypatch, tmp_path) -> None:
    monkeypatch.chdir(tmp_path)
    monkeypatch.setattr("ocr_pipeline.config.user_config_dir", lambda: tmp_path / "missing")
    r = check_config()
    assert r.status == "warn"
    assert "init" in (r.hint or "")


def test_check_db_writes_temp_file(tmp_path, monkeypatch) -> None:
    db = tmp_path / "sub" / "test.db"
    monkeypatch.setattr(settings.watch, "db_path", str(db))
    r = check_db()
    assert r.status == "ok"
    assert db.exists()


def test_check_output_dir_creates_path(tmp_path, monkeypatch) -> None:
    out = tmp_path / "deep" / "output"
    monkeypatch.setattr(settings.output, "base_directory", str(out))
    r = check_output_dir()
    assert r.status == "ok"
    assert out.is_dir()


def test_exit_code_for() -> None:
    from ocr_pipeline.doctor import CheckResult

    assert exit_code_for([]) == 0
    assert exit_code_for([CheckResult("a", "ok", "x")]) == 0
    assert exit_code_for([CheckResult("a", "warn", "x")]) == 0
    assert exit_code_for([CheckResult("a", "fail", "x")]) == 1


def test_run_checks_returns_all_categories() -> None:
    results = run_checks()
    names = {r.name for r in results}
    assert {"Tesseract", "PaddleOCR", "Python", "Config"}.issubset(names)


def test_render_writes_table(capsys) -> None:
    from rich.console import Console

    from ocr_pipeline.doctor import CheckResult

    console = Console(file=__import__("sys").stdout)
    render(
        [CheckResult("A", "ok", "fine"), CheckResult("B", "fail", "bad", hint="fix me")], console
    )
    out = capsys.readouterr().out
    assert "A" in out and "B" in out


def test_doctor_cli_runs(monkeypatch) -> None:
    from ocr_pipeline.doctor import CheckResult

    monkeypatch.setattr("ocr_pipeline.cli.run_checks", lambda: [CheckResult("X", "ok", "ok")])
    result = runner.invoke(app, ["doctor"])
    assert result.exit_code == 0
    assert "X" in result.output


def test_doctor_cli_exit_code_on_fail(monkeypatch) -> None:
    from ocr_pipeline.doctor import CheckResult

    monkeypatch.setattr("ocr_pipeline.cli.run_checks", lambda: [CheckResult("X", "fail", "bad")])
    result = runner.invoke(app, ["doctor"])
    assert result.exit_code == 1
