from __future__ import annotations

from copy import deepcopy
from pathlib import Path

import pytest

from crowley_diary.alignment import align_page_blocks
from crowley_diary.builder import build_candidate_page
from crowley_diary.diagnostics import detect_cross_page_duplicates
from crowley_diary.pilot import validate_pilot_selection
from crowley_diary.source import SourcePackage
from crowley_diary.util import load_json, project_root, write_json
from crowley_diary.validation import validate_page


def _candidate181(source_root: Path) -> tuple[SourcePackage, dict]:
    source = SourcePackage(source_root)
    draft = load_json(project_root() / "tests" / "fixtures" / "model" / "page-181.json")
    page = build_candidate_page(
        draft=draft,
        source=source,
        page=181,
        run_id="alignment-test",
        backend="fixture",
        model="fixture",
        reasoning_effort="none",
        prompt_path=project_root() / "prompts" / "transcribe-page.md",
        schema_path=project_root() / "schema" / "model-output.schema.json",
        context_pages=[],
        created_at="2026-08-08T18:00:00Z",
    )
    return source, page


def test_partial_and_absent_tsv_alignment_are_non_blocking(source_root: Path) -> None:
    source, partial = _candidate181(source_root)
    segment = partial["blocks"][0]["content"][0]["lines"][0]["segments"][0]
    segment["text"] = "Monday July zzz qqq"
    align_page_blocks(partial, source)
    assert partial["blocks"][0]["evidence"]["status"] == "partial"
    assert validate_page(partial, source).valid

    absent = deepcopy(partial)
    absent["blocks"][0]["content"][0]["lines"][0]["segments"][0]["text"] = (
        "zzzxqv qqqxyz"
    )
    align_page_blocks(absent, source)
    assert absent["blocks"][0]["evidence"]["status"] == "unaligned"
    assert absent["blocks"][0]["evidence"]["bbox"] is None
    assert validate_page(absent, source).valid


def _renderable_page(number: int, text: str) -> dict:
    return {
        "source": {"pdf_page": number},
        "blocks": [
            {
                "type": "text",
                "lines": [
                    {
                        "line_number": 1,
                        "segments": [{"type": "text", "text": text}],
                        "line_end": "block_end",
                    }
                ],
            }
        ],
    }


def test_duplicate_cross_page_text_is_reported_but_not_rewritten() -> None:
    repeated = "one two three four five six seven eight nine"
    pages = {
        10: _renderable_page(10, f"Earlier words {repeated}"),
        11: _renderable_page(11, f"{repeated} later words"),
    }
    before = deepcopy(pages)
    findings = detect_cross_page_duplicates(pages)
    assert findings == [
        {
            "left_page": 10,
            "right_page": 11,
            "word_count": 9,
            "normalized_text": repeated,
        }
    ]
    assert pages == before


def test_production_pilot_selection_is_exactly_pages_41_through_60(
    source_root: Path,
) -> None:
    source = SourcePackage(source_root)
    selection_path = project_root() / "pilot" / "selection.json"
    selection, pages = validate_pilot_selection(selection_path, source)
    assert pages == list(range(41, 61))
    assert selection["page_count"] == 20
    assert {source.segment_for_page(page)["id"] for page in pages} == {"book-02"}


def test_pilot_selection_rejects_nonconsecutive_or_evaluation_overlap(
    source_root: Path, tmp_path: Path
) -> None:
    source = SourcePackage(source_root)
    bad = tmp_path / "bad.json"
    write_json(bad, {"cases": [{"page": page} for page in range(1, 21)]})
    with pytest.raises(ValueError, match="overlaps evaluation"):
        validate_pilot_selection(bad, source)

    write_json(
        bad,
        {"cases": [{"page": page} for page in [*range(41, 60), 61]]},
    )
    # This is still consecutive; make one actual gap while retaining 20 unique pages.
    value = load_json(bad)
    value["cases"][-1]["page"] = 62
    write_json(bad, value)
    with pytest.raises(ValueError, match="consecutive"):
        validate_pilot_selection(bad, source)


def test_committed_pilot_results_validate_and_remain_diagnostic(
    source_root: Path,
) -> None:
    source = SourcePackage(source_root)
    latest = load_json(project_root() / "pilot" / "latest-run.json")
    run_root = project_root() / "pilot" / "runs" / latest["run_id"]
    manifest = load_json(run_root / "run-manifest.json")
    assert manifest["status"] == "complete"
    assert manifest["pages"] == list(range(41, 61))
    assert manifest["failed_pages"] == []
    assert manifest["final_validation_failed_pages"] == []

    for page_number in range(41, 61):
        page_id = f"page-{page_number:03d}"
        page_root = run_root / "pages" / page_id
        candidate = load_json(run_root / "candidates" / f"{page_id}.json")
        assert validate_page(candidate, source).valid
        assert candidate["review"] == {
            "status": "model_candidate",
            "authority": "diagnostic_only",
            "reviewer": None,
            "reviewed_at": None,
            "notes": ["Model candidate only; the page image remains authoritative."],
        }
        assert load_json(page_root / "final-validation.json")["valid"]
        for required in ("current-ocr.txt", "neighbor-context.json", "input-evidence.json"):
            assert (page_root / required).is_file()
        status = load_json(page_root / "status.json")
        final_attempt = page_root / f"attempt-{status['attempt_count']:02d}"
        for required in (
            "command.json",
            "stdout.jsonl",
            "stderr.log",
            "raw-final-response.txt",
            "backend-result.json",
            "model-validation.json",
            "model-draft.json",
        ):
            assert (final_attempt / required).is_file()

    assert load_json(run_root / "reconciliation.json")["validation"]["valid"]
    assert load_json(run_root / "cross-page-duplicates.json")["findings"] == []
    assert (run_root / "review" / "report.md").is_file()
    assert (run_root / "review" / "report.json").is_file()
    assert (run_root / "review" / "index.html").is_file()
    assert len(list((run_root / "review" / "pages").glob("page-*.html"))) == 20
    index_html = (run_root / "review" / "index.html").read_text(encoding="utf-8")
    assert "All 20 pages are unapproved" in index_html
    page_html = (run_root / "review" / "pages" / "page-043.html").read_text(
        encoding="utf-8"
    )
    assert "Authoritative scan" in page_html
    assert "Raw OCR" in page_html
    assert "Model candidate JSON" in page_html
    assert "Raw final model response" in page_html
    assert "Viewing this page does not approve" in page_html
    report = load_json(run_root / "review" / "report.json")
    assert all(item["editorial_notes"] is not None for item in report["pages"])
    assert all(Path(item["input_ocr"]).is_file() for item in report["pages"])
    assert all(Path(item["raw_final_response"]).is_file() for item in report["pages"])
    assert (run_root / "pages" / "page-044" / "attempt-00-interrupted-sol-xhigh" / "interruption.json").is_file()
