from __future__ import annotations

from copy import deepcopy
from pathlib import Path
import shutil

from crowley_diary.owner_review import (
    approve_page,
    build_review_bundle,
    mark_needs_work,
    summarize_owner_review,
)
from crowley_diary.source import SourcePackage
from crowley_diary.util import load_json, project_root, sha256_file, write_json
from crowley_diary.validation import validate_page


def _review_root(tmp_path: Path, page: int) -> Path:
    root = tmp_path / "project"
    page_id = f"page-{page:03d}"
    source_root = project_root()
    write_json(
        root / "evaluation" / "selection.json",
        {"page_count": 1, "cases": [{"page": page, "reason": "test challenge"}]},
    )
    diagnostic = source_root / "evaluation" / "goldens" / f"{page_id}.json"
    destination = root / "evaluation" / "goldens" / f"{page_id}.json"
    destination.parent.mkdir(parents=True, exist_ok=True)
    shutil.copyfile(diagnostic, destination)
    return root


def test_building_review_bundle_does_not_create_approval(
    source_root: Path, tmp_path: Path
) -> None:
    root = _review_root(tmp_path, 39)
    candidate_source = (
        project_root()
        / "artifacts"
        / "runs"
        / "20260808T180301Z-eval"
        / "candidates"
        / "page-039.json"
    )
    candidate = root / "artifacts" / "runs" / "test-run" / "candidates" / "page-039.json"
    candidate.parent.mkdir(parents=True, exist_ok=True)
    shutil.copyfile(candidate_source, candidate)
    write_json(
        root / "evaluation" / "candidate-index.json",
        {
            "candidates": [
                {
                    "page": 39,
                    "run_id": "test-run",
                    "path": str(candidate.relative_to(root)),
                }
            ]
        },
    )
    write_json(
        root / "artifacts" / "runs" / "test-run" / "pages" / "page-039" / "status.json",
        {"status": "valid_candidate"},
    )
    output = root / "evaluation" / "owner-review"
    result = build_review_bundle(
        source=SourcePackage(source_root), output=output, root=root
    )
    assert result["owner_approvals_created"] == 0
    assert (output / "index.html").is_file()
    assert (output / "pages" / "page-039.html").is_file()
    assert not (root / "edition" / "owner-approved" / "page-039.json").exists()
    assert not (output / "states" / "page-039.json").exists()


def test_explicit_approve_as_is_preserves_diagnostic(
    source_root: Path, tmp_path: Path
) -> None:
    root = _review_root(tmp_path, 39)
    diagnostic = root / "evaluation" / "goldens" / "page-039.json"
    before_hash = sha256_file(diagnostic)
    result = approve_page(
        source=SourcePackage(source_root),
        page=39,
        owner="Diary Owner",
        root=root,
    )
    assert result["status"] == "owner_approved"
    assert sha256_file(diagnostic) == before_hash
    approved = load_json(root / "edition" / "owner-approved" / "page-039.json")
    assert approved["review"]["status"] == "owner_approved"
    assert approved["review"]["authority"] == "owner_approved_edition"
    assert approved["provenance"]["steps"][-1]["action"] == "approve_as_is"
    assert validate_page(approved, SourcePackage(source_root)).valid


def test_explicit_corrected_approval_preserves_inputs_and_records_lineage(
    source_root: Path, tmp_path: Path
) -> None:
    root = _review_root(tmp_path, 110)
    diagnostic_path = root / "evaluation" / "goldens" / "page-110.json"
    diagnostic_hash = sha256_file(diagnostic_path)
    corrected = deepcopy(load_json(diagnostic_path))
    corrected["blocks"][0]["lines"][0]["segments"][0]["text"] = (
        "One book skipped - from July 25, 1897 to Nov. 28."
    )
    corrected_path = tmp_path / "owner-correction.json"
    write_json(corrected_path, corrected)
    correction_hash = sha256_file(corrected_path)

    approve_page(
        source=SourcePackage(source_root),
        page=110,
        owner="Diary Owner",
        corrected_json=corrected_path,
        root=root,
    )
    assert sha256_file(diagnostic_path) == diagnostic_hash
    submission = root / "evaluation" / "owner-review" / "submissions" / "page-110.json"
    assert sha256_file(submission) == correction_hash
    approved = load_json(root / "edition" / "owner-approved" / "page-110.json")
    step = approved["provenance"]["steps"][-1]
    assert step["action"] == "approve_corrected"
    assert "textual_transcription" in step["change_categories"]
    state = load_json(
        root / "evaluation" / "owner-review" / "states" / "page-110.json"
    )
    assert state["events"][-1]["diagnostic_sha256"] == diagnostic_hash
    assert state["events"][-1]["owner_textual_change"] is True


def test_needs_work_stays_unapproved_and_is_counted_as_deferred(tmp_path: Path) -> None:
    root = _review_root(tmp_path, 39)
    result = mark_needs_work(
        page=39, owner="Diary Owner", reason="Text remains unclear", root=root
    )
    assert result["owner_approved_edition_created"] is False
    assert not (root / "edition" / "owner-approved" / "page-039.json").exists()
    summary = summarize_owner_review(root)
    assert summary["number_approved"] == 0
    assert summary["number_rejected_or_deferred"] == 1
    assert summary["accuracy_calculated_against_diagnostics"] is False


def test_committed_owner_approved_regression_lineage_is_complete(
    source_root: Path,
) -> None:
    source = SourcePackage(source_root)
    selection = load_json(project_root() / "evaluation" / "selection.json")
    selected_pages = {case["page"] for case in selection["cases"]}
    approved_root = project_root() / "edition" / "owner-approved"
    approved_paths = sorted(
        path
        for path in approved_root.glob("page-*.json")
        if int(path.stem.split("-")[1]) in selected_pages
    )
    assert len(approved_paths) == len(selected_pages) == 14
    corrected = {181, 232}
    for path in approved_paths:
        page = load_json(path)
        page_number = page["source"]["pdf_page"]
        diagnostic = load_json(
            project_root() / "evaluation" / "goldens" / path.name
        )
        assert diagnostic["review"]["status"] == "agent_reviewed"
        assert diagnostic["review"]["authority"] == "diagnostic_only"
        assert page["review"]["status"] == "owner_approved"
        assert page["review"]["authority"] == "owner_approved_edition"
        owner_step = page["provenance"]["steps"][-1]
        assert owner_step["kind"] == "owner_review"
        assert owner_step["action"] == (
            "approve_corrected" if page_number in corrected else "approve_as_is"
        )
        assert validate_page(page, source).valid

    summary = summarize_owner_review()
    assert summary["number_approved"] == 14
    assert summary["number_approved_as_is"] == 12
    assert summary["number_corrected_by_owner"] == 2
    assert summary["pages_requiring_owner_textual_changes"] == [181, 232]

    all_approved = sorted(approved_root.glob("page-*.json"))
    assert len(all_approved) == 34
    assert {
        load_json(path)["source"]["pdf_page"] for path in all_approved
    } == selected_pages | set(range(41, 61))
