from __future__ import annotations

import importlib.util
import json
import subprocess
import sys
from pathlib import Path

from skills._shared.python.skill_testing import assert_entrypoints_exist, assert_skill_contract


def _skill_root() -> Path:
    return Path(__file__).resolve().parents[1]


def _load_module():
    module_path = _skill_root() / "bin" / "tracking_issue_closeout.py"
    spec = importlib.util.spec_from_file_location("tracking_issue_closeout", module_path)
    assert spec and spec.loader
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    return module


def _state_body(*, state_status: str = "complete", row_statuses: tuple[str, str] = ("done", "deferred")) -> str:
    return "\n".join(
        [
            "<!-- execute-from-tracking-issue:state:v1 -->",
            "## Execution State",
            "",
            f"- Status: {state_status}",
            "- Target scope: whole issue",
            "- Execution window: whole issue",
            "- Current task: final closeout",
            "- Next task: none",
            "- Last updated: 2026-05-21 19:35 CST",
            "- Branch/commit/PR: merged feature branch",
            "",
            "<details>",
            "<summary>Full task ledger</summary>",
            "",
            "| ID | Status | Task | Evidence | Notes |",
            "| --- | --- | --- | --- | --- |",
            f"| 1.1 | {row_statuses[0]} | First sprint | state evidence | Complete. |",
            f"| 1.2 | {row_statuses[1]} | Final sprint | state evidence | Complete or deferred. |",
            "",
            "</details>",
        ]
    )


def _issue_payload() -> dict:
    return {
        "number": 20,
        "title": "Track plan: Skills Plugin Reorganization (Hybrid C)",
        "state": "OPEN",
        "url": "https://github.com/graysurf/claude-kit/issues/20",
        "body": "## Plan Tracking\n\nInitial tracker body.",
        "comments": [
            {
                "url": "https://github.com/graysurf/claude-kit/issues/20#issuecomment-source",
                "createdAt": "2026-05-19T18:07:57Z",
                "body": "\n".join(
                    [
                        "<!-- plan-tracking-issue:snapshot:v1 kind=source -->",
                        "## Source snapshot",
                        "",
                        "Historical reference: #999",
                        "Source text quotes `<!-- execute-from-tracking-issue:session:v1 -->` and PR #404,",
                        "but this snapshot is not itself an execution comment.",
                    ]
                ),
            },
            {
                "url": "https://github.com/graysurf/claude-kit/issues/20#issuecomment-plan",
                "createdAt": "2026-05-19T18:08:04Z",
                "body": "<!-- plan-tracking-issue:snapshot:v1 kind=plan -->\n## Plan snapshot\n",
            },
            {
                "url": "https://github.com/graysurf/claude-kit/issues/20#issuecomment-state",
                "createdAt": "2026-05-19T18:46:04Z",
                "body": _state_body(),
            },
            {
                "url": "https://github.com/graysurf/claude-kit/issues/20#issuecomment-final",
                "createdAt": "2026-05-19T19:35:00Z",
                "body": "\n".join(
                    [
                        "<!-- execute-from-tracking-issue:session:v1 -->",
                        "## All 6 sprints complete — handoff to user for final review + close",
                        "",
                        "- PR: #21",
                        "- pull request #22",
                        "- PR URL: https://github.com/graysurf/claude-kit/pull/23",
                        "- PR path: /pull/24",
                        "- Final PR #25 merged.",
                        "- Decision #9 was retained from planning notes.",
                        "- Task #5 was handled before this session.",
                        "- Related issue #401 was the source of the repair need.",
                        "- Historical bare reference #123 should not be linked as a PR.",
                        "- Validation: `bash scripts/ci/all.sh` green; hook tests pass",
                        "- Blockers: none",
                    ]
                ),
            },
        ],
    }


def _closed_issue_payload() -> dict:
    payload = _issue_payload()
    payload["state"] = "CLOSED"
    return payload


def _closeout_marker_payload() -> dict:
    payload = _closed_issue_payload()
    payload["comments"].append(
        {
            "url": "https://github.com/graysurf/claude-kit/issues/20#issuecomment-closeout",
            "createdAt": "2026-05-19T19:40:00Z",
            "body": "<!-- tracking-issue-closeout:v1 -->\n## Tracking Issue Closeout\n",
        }
    )
    return payload


def test_workflows_issue_tracking_issue_closeout_contract() -> None:
    assert_skill_contract(_skill_root())


def test_workflows_issue_tracking_issue_closeout_entrypoints_exist() -> None:
    assert_entrypoints_exist(_skill_root(), ["scripts/tracking-issue-closeout.sh"])


def test_tracking_issue_closeout_analyzes_issue_twenty_shape() -> None:
    module = _load_module()
    issue = module.issue_from_payload(_issue_payload())
    model = module.analyze_issue(
        issue,
        review_approved=True,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
    )

    assert model.ready
    assert model.completion.ready
    assert "<summary>Full task ledger</summary>" in issue.comments[2].body
    assert issue.comments[2].body.index("- Status: complete") < issue.comments[2].body.index("<details>")
    assert model.pr_numbers == [21, 22, 23, 24, 25]
    assert "scripts/ci/all.sh" in model.validation_summary

    dashboard = module.render_dashboard(model, closeout_comment_url="https://github.com/example/repo/issues/20#issuecomment-close", closed=True)
    assert "## Final Dashboard" in dashboard
    assert "source snapshot" in dashboard
    assert "plan snapshot" in dashboard
    assert "execution state" in dashboard
    assert "#21, #22, #23, #24, #25" in dashboard
    assert "tracking-issue-closeout" not in dashboard.lower()

    comment = module.render_closeout_comment(model, dry_run=False)
    assert "<!-- tracking-issue-closeout:v1 -->" in comment
    assert "Action: Posted final dashboard and closed the issue as completed." in comment


def test_tracking_issue_closeout_requires_close_approval() -> None:
    module = _load_module()
    issue = module.issue_from_payload(_issue_payload())
    model = module.analyze_issue(
        issue,
        review_approved=False,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
    )

    assert not model.ready
    failed = {check.name: check.detail for check in model.checks if check.status == "fail"}
    assert failed["close approval"] == "missing close approval"


def test_tracking_issue_closeout_blocks_closed_issue_without_repair_mode() -> None:
    module = _load_module()
    issue = module.issue_from_payload(_closed_issue_payload())
    model = module.analyze_issue(
        issue,
        review_approved=True,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
    )

    assert not model.ready
    failed = {check.name: check.detail for check in model.checks if check.status == "fail"}
    assert failed["issue state"] == "state=CLOSED"


def test_tracking_issue_closeout_repair_mode_accepts_closed_issue() -> None:
    module = _load_module()
    issue = module.issue_from_payload(_closed_issue_payload())
    model = module.analyze_issue(
        issue,
        review_approved=True,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
        repair_closed=True,
    )

    assert model.ready
    assert model.repair_only
    assert model.pr_numbers == [21, 22, 23, 24, 25]

    dashboard = module.render_dashboard(model, closeout_comment_url=None, closed=False)
    assert "dashboard repaired; issue was already closed" in dashboard
    assert "not posted in repair-only mode" in dashboard


def test_tracking_issue_closeout_blocks_without_snapshots() -> None:
    module = _load_module()
    payload = _issue_payload()
    payload["comments"] = payload["comments"][2:]
    issue = module.issue_from_payload(payload)
    model = module.analyze_issue(
        issue,
        review_approved=True,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
    )

    assert not model.ready
    failed = {check.name for check in model.checks if check.status == "fail"}
    assert {"source snapshot", "plan snapshot"}.issubset(failed)


def test_tracking_issue_closeout_script_dry_run_writes_outputs(tmp_path: Path) -> None:
    issue_json = tmp_path / "issue.json"
    body_out = tmp_path / "body.md"
    comment_out = tmp_path / "comment.md"
    report_out = tmp_path / "report.json"
    issue_json.write_text(json.dumps(_issue_payload()), "utf-8")

    proc = subprocess.run(
        [
            str(_skill_root() / "scripts" / "tracking-issue-closeout.sh"),
            "--issue-json",
            str(issue_json),
            "--dry-run",
            "--review-approved",
            "--skip-pr-checks",
            "--body-out",
            str(body_out),
            "--comment-out",
            str(comment_out),
            "--report-out",
            str(report_out),
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    assert proc.returncode == 0, proc.stderr
    assert "status=dry-run" in proc.stdout
    assert "ready=true" in proc.stdout
    assert "## Final Dashboard" in body_out.read_text("utf-8")
    assert "<!-- tracking-issue-closeout:v1 -->" in comment_out.read_text("utf-8")
    report = json.loads(report_out.read_text("utf-8"))
    assert report["ready"] is True
    assert report["completion_ready"] is True
    assert report["closed"] is False
    assert report["repair_only"] is False
    assert report["finalized_closed_issue"] is False


def test_tracking_issue_closeout_script_repair_closed_dry_run_writes_only_dashboard(tmp_path: Path) -> None:
    issue_json = tmp_path / "issue.json"
    body_out = tmp_path / "body.md"
    comment_out = tmp_path / "comment.md"
    report_out = tmp_path / "report.json"
    issue_json.write_text(json.dumps(_closed_issue_payload()), "utf-8")

    proc = subprocess.run(
        [
            str(_skill_root() / "scripts" / "tracking-issue-closeout.sh"),
            "--issue-json",
            str(issue_json),
            "--dry-run",
            "--repair-closed",
            "--review-approved",
            "--skip-pr-checks",
            "--body-out",
            str(body_out),
            "--comment-out",
            str(comment_out),
            "--report-out",
            str(report_out),
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    assert proc.returncode == 0, proc.stderr
    assert "status=repair-dry-run" in proc.stdout
    assert "mode=repair-closed" in proc.stdout
    assert "## Final Dashboard" in body_out.read_text("utf-8")
    assert "dashboard repaired; issue was already closed" in body_out.read_text("utf-8")
    assert not comment_out.exists()
    report = json.loads(report_out.read_text("utf-8"))
    assert report["ready"] is True
    assert report["closed"] is False
    assert report["repair_only"] is True
    assert report["finalized_closed_issue"] is False
    assert report["mode"] == "repair-closed"
    assert report["closeout_comment_url"] is None


def test_tracking_issue_closeout_blocks_incomplete_latest_state() -> None:
    module = _load_module()
    payload = _issue_payload()
    payload["comments"][2]["body"] = _state_body(state_status="in-progress", row_statuses=("done", "done"))
    issue = module.issue_from_payload(payload)
    model = module.analyze_issue(
        issue,
        review_approved=True,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
    )

    assert not model.ready
    assert not model.completion.ready
    failed = {check.name: check.detail for check in model.checks if check.status == "fail"}
    assert "state status is in-progress; expected complete" in failed["execution completion"]
    assert "post a complete execute-from-tracking-issue:state:v1 comment first" in failed["execution completion"]


def test_tracking_issue_closeout_blocks_pending_task_row() -> None:
    module = _load_module()
    payload = _issue_payload()
    payload["comments"][2]["body"] = _state_body(state_status="complete", row_statuses=("done", "pending"))
    issue = module.issue_from_payload(payload)
    model = module.analyze_issue(
        issue,
        review_approved=True,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
    )

    assert not model.ready
    assert model.completion.incomplete_rows == [
        {
            "ID": "1.2",
            "Status": "pending",
            "Task": "Final sprint",
            "Evidence": "state evidence",
            "Notes": "Complete or deferred.",
        }
    ]
    failed = {check.name: check.detail for check in model.checks if check.status == "fail"}
    assert "incomplete task rows: 1.2=pending" in failed["execution completion"]


def test_tracking_issue_closeout_repair_closed_also_blocks_incomplete_state() -> None:
    module = _load_module()
    payload = _closed_issue_payload()
    payload["comments"][2]["body"] = _state_body(state_status="in-progress", row_statuses=("done", "done"))
    issue = module.issue_from_payload(payload)
    model = module.analyze_issue(
        issue,
        review_approved=True,
        review_comment_url=None,
        repo=None,
        skip_pr_checks=True,
        dry_run=True,
        repair_closed=True,
    )

    assert not model.ready
    failed = {check.name: check.detail for check in model.checks if check.status == "fail"}
    assert "state status is in-progress; expected complete" in failed["execution completion"]


def test_tracking_issue_closeout_script_finalize_closed_dry_run_writes_missing_marker(tmp_path: Path) -> None:
    issue_json = tmp_path / "issue.json"
    body_out = tmp_path / "body.md"
    comment_out = tmp_path / "comment.md"
    report_out = tmp_path / "report.json"
    issue_json.write_text(json.dumps(_closed_issue_payload()), "utf-8")

    proc = subprocess.run(
        [
            str(_skill_root() / "scripts" / "tracking-issue-closeout.sh"),
            "--issue-json",
            str(issue_json),
            "--dry-run",
            "--repair-closed",
            "--finalize-closed",
            "--review-approved",
            "--skip-pr-checks",
            "--body-out",
            str(body_out),
            "--comment-out",
            str(comment_out),
            "--report-out",
            str(report_out),
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    assert proc.returncode == 0, proc.stderr
    assert "mode=repair-closed-finalize" in proc.stdout
    assert "finalized_closed_issue=true" in proc.stdout
    assert "pending closed-issue finalization comment" in body_out.read_text("utf-8")
    assert "<!-- tracking-issue-closeout:v1 -->" in comment_out.read_text("utf-8")
    report = json.loads(report_out.read_text("utf-8"))
    assert report["mode"] == "repair-closed-finalize"
    assert report["repair_only"] is False
    assert report["finalized_closed_issue"] is True


def test_tracking_issue_closeout_script_finalize_closed_does_not_duplicate_existing_marker(tmp_path: Path) -> None:
    issue_json = tmp_path / "issue.json"
    body_out = tmp_path / "body.md"
    comment_out = tmp_path / "comment.md"
    report_out = tmp_path / "report.json"
    issue_json.write_text(json.dumps(_closeout_marker_payload()), "utf-8")

    proc = subprocess.run(
        [
            str(_skill_root() / "scripts" / "tracking-issue-closeout.sh"),
            "--issue-json",
            str(issue_json),
            "--dry-run",
            "--repair-closed",
            "--finalize-closed",
            "--review-approved",
            "--skip-pr-checks",
            "--body-out",
            str(body_out),
            "--comment-out",
            str(comment_out),
            "--report-out",
            str(report_out),
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    assert proc.returncode == 0, proc.stderr
    assert "mode=repair-closed" in proc.stdout
    assert "finalized_closed_issue=false" in proc.stdout
    assert "existing closeout comment" in body_out.read_text("utf-8")
    assert not comment_out.exists()
    report = json.loads(report_out.read_text("utf-8"))
    assert report["repair_only"] is True
    assert report["finalized_closed_issue"] is False
