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_lifecycle.py"
    spec = importlib.util.spec_from_file_location("tracking_issue_lifecycle", 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 = "in-progress",
    row_statuses: tuple[str, str] = ("done", "in-progress"),
    table_header: str = "| ID | Status | Task | Evidence | Notes |",
) -> 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: Task 1.2 - implement lifecycle issue parsing and schema audit",
            "- Next task: Task 2.1 - add dashboard and drift tests",
            "- Last updated: 2026-05-21 13:10 CST",
            "- Branch/commit/PR: feat/tracking-issue-lifecycle-state",
            "",
            "<details>",
            "<summary>Full task ledger</summary>",
            "",
            table_header,
            "| --- | --- | --- | --- | --- |" if "Evidence" in table_header else "| --- | --- | --- | --- |",
            f"| 1.1 | {row_statuses[0]} | Add failing coverage | focused tests | Tests now pin the helper contract. |",
            f"| 1.2 | {row_statuses[1]} | Implement lifecycle issue parsing | helper module | Add parser and report schema. |",
            "",
            "</details>",
        ]
    )


def _issue_payload() -> dict:
    return {
        "number": 270,
        "title": "Harden tracking issue lifecycle state and dashboard rendering",
        "state": "OPEN",
        "url": "https://github.com/graysurf/agent-kit/issues/270",
        "body": "\n".join(
            [
                "## Current Dashboard",
                "",
                "- Status: in-progress",
                "- Source snapshot: https://github.com/graysurf/agent-kit/issues/270#issuecomment-source",
                "- Plan snapshot: https://github.com/graysurf/agent-kit/issues/270#issuecomment-plan",
                "- Execution state: https://github.com/graysurf/agent-kit/issues/270#issuecomment-old-state",
                "- Latest session: https://github.com/graysurf/agent-kit/issues/270#issuecomment-old-session",
                "- Latest validation: https://github.com/graysurf/agent-kit/issues/270#issuecomment-old-validation",
            ]
        ),
        "comments": [
            {
                "url": "https://github.com/graysurf/agent-kit/issues/270#issuecomment-source",
                "createdAt": "2026-05-21T04:00:00Z",
                "body": "<!-- plan-tracking-issue:snapshot:v1 kind=source -->\n## Source snapshot\n",
            },
            {
                "url": "https://github.com/graysurf/agent-kit/issues/270#issuecomment-plan",
                "createdAt": "2026-05-21T04:01:00Z",
                "body": "<!-- plan-tracking-issue:snapshot:v1 kind=plan -->\n## Plan snapshot\n",
            },
            {
                "url": "https://github.com/graysurf/agent-kit/issues/270#issuecomment-state",
                "createdAt": "2026-05-21T04:02:00Z",
                "body": _state_body(),
            },
            {
                "url": "https://github.com/graysurf/agent-kit/issues/270#issuecomment-session",
                "createdAt": "2026-05-21T04:03:00Z",
                "body": "\n".join(
                    [
                        "<!-- execute-from-tracking-issue:session:v1 -->",
                        "## Lifecycle Helper Sprint 1 Progress",
                        "",
                        "- Status: in-progress",
                        "- PR: #272",
                        "- Validation: focused tests pending",
                        "- Blockers: none",
                    ]
                ),
            },
            {
                "url": "https://github.com/graysurf/agent-kit/issues/270#issuecomment-validation",
                "createdAt": "2026-05-21T04:04:00Z",
                "body": "\n".join(
                    [
                        "<!-- execute-from-tracking-issue:validation:v1 -->",
                        "## Lifecycle Helper Validation",
                        "",
                        "- Status: pass",
                        "",
                        "| Command | Status | Summary |",
                        "| --- | --- | --- |",
                        "| `scripts/check.sh --tests -- -k execute_from_tracking_issue` | pass | focused tests passed |",
                    ]
                ),
            },
        ],
    }


def _completion_issue_payload() -> dict:
    payload = _issue_payload()
    payload["body"] = "\n".join(
        [
            "## Current Dashboard",
            "",
            "- Status: complete",
            "- Source snapshot: https://github.com/graysurf/agent-kit/issues/270#issuecomment-source",
            "- Plan snapshot: https://github.com/graysurf/agent-kit/issues/270#issuecomment-plan",
            "- Execution state: https://github.com/graysurf/agent-kit/issues/270#issuecomment-state",
            "- Latest session: https://github.com/graysurf/agent-kit/issues/270#issuecomment-session",
            "- Latest validation: https://github.com/graysurf/agent-kit/issues/270#issuecomment-validation",
        ]
    )
    payload["comments"][2]["body"] = _state_body(state_status="complete", row_statuses=("done", "deferred"))
    return payload


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


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


def test_workflows_issue_execute_from_tracking_issue_has_lifecycle_helper() -> None:
    text = (_skill_root() / "SKILL.md").read_text(encoding="utf-8")

    assert "`scripts/tracking-issue-lifecycle.sh`" in text
    assert "execute-from-tracking-issue.lifecycle-report.v1" in text
    assert "body-only dashboard repair" in text


def test_execute_from_tracking_issue_requires_issue_hosted_snapshots() -> None:
    text = (_skill_root() / "SKILL.md").read_text(encoding="utf-8")

    assert "`<!-- plan-tracking-issue:snapshot:v1 kind=source -->`" in text
    assert "`<!-- plan-tracking-issue:snapshot:v1 kind=plan -->`" in text
    assert "`<!-- execute-from-tracking-issue:state:v1 -->`" in text
    assert "<summary>Full task ledger</summary>" in text
    assert "Prefer issue-hosted source and plan snapshots over local files." in text
    assert "marker lines and metadata outside collapsed sections" in text
    assert "<summary>Source content</summary>" in text
    assert "<summary>Plan content</summary>" in text
    assert "Do not wrap local Markdown snapshots in code fences" in text
    assert "If both issue snapshots and local source/plan files are missing, stop" in text


def test_execute_from_tracking_issue_keeps_issue_as_durable_record() -> None:
    text = (_skill_root() / "SKILL.md").read_text(encoding="utf-8")

    assert "Issue body is the mutable dashboard" in text
    assert "comments are append-only evidence" in text
    assert "GitHub issue comments remain the durable record" in text
    assert "Close the issue only when the user asked for closure" in text
    assert "`dispatch-plan-delivery`" in text


def test_execute_from_tracking_issue_defaults_to_pr_without_merge_or_close() -> None:
    text = (_skill_root() / "SKILL.md").read_text(encoding="utf-8")
    normalized_text = " ".join(text.split())

    assert "open a PR by default for the completed implementation scope" in text
    assert "unless the user requests local-only work, direct push, or project policy says otherwise" in normalized_text
    assert "Do not merge the PR unless the user explicitly requests delivery" in text
    assert "`deliver-github-pr`" in text


def test_execute_from_tracking_issue_lifecycle_audit_detects_dashboard_drift() -> None:
    module = _load_module()
    issue = module.issue_from_payload(_issue_payload())
    model = module.analyze_issue(issue, repair_body=False)

    assert model.ready
    assert model.state_columns == ["ID", "Status", "Task", "Evidence", "Notes"]
    assert model.current_state["Current task"] == "Task 1.2 - implement lifecycle issue parsing and schema audit"
    assert model.dashboard_drift
    assert model.stale_links == ["execution state", "latest session", "latest validation"]

    dashboard = module.render_dashboard(model)
    assert "## Current Dashboard" in dashboard
    assert "Task 1.2 - implement lifecycle issue parsing and schema audit" in dashboard
    assert "https://github.com/graysurf/agent-kit/issues/270#issuecomment-state" in dashboard
    assert "https://github.com/graysurf/agent-kit/issues/270#issuecomment-session" in dashboard
    assert "https://github.com/graysurf/agent-kit/issues/270#issuecomment-validation" in dashboard
    assert "Rendered dashboard links: current" in dashboard
    assert "Stale links: execution state" not in dashboard
    assert "Dashboard drift: yes" not in dashboard

    report = module.report_payload(model, dry_run=True)
    assert report["schema"] == "execute-from-tracking-issue.lifecycle-report.v1"
    assert report["dashboard_drift"] is True
    assert report["stale_links"] == ["execution state", "latest session", "latest validation"]
    assert report["completion_ready"] is False
    assert "state status is in-progress; expected complete" in report["completion_blockers"]


def test_execute_from_tracking_issue_completion_gate_accepts_complete_ledger() -> None:
    module = _load_module()
    issue = module.issue_from_payload(_completion_issue_payload())
    model = module.analyze_issue(issue, repair_body=False)

    assert model.ready
    assert model.completion.ready
    assert model.completion.blockers == []
    report = module.report_payload(model, dry_run=True)
    assert report["completion_ready"] is True
    assert report["incomplete_state_rows"] == []


def test_execute_from_tracking_issue_accepts_visible_state_summary_and_collapsed_ledger() -> None:
    module = _load_module()
    payload = _completion_issue_payload()
    state_body = payload["comments"][2]["body"]

    assert state_body.splitlines()[0] == "<!-- execute-from-tracking-issue:state:v1 -->"
    assert "<details>" in state_body
    assert "<summary>Full task ledger</summary>" in state_body
    assert state_body.index("- Status: complete") < state_body.index("<details>")
    assert state_body.index("<details>") < state_body.index("| ID | Status | Task | Evidence | Notes |")

    issue = module.issue_from_payload(payload)
    model = module.analyze_issue(issue, repair_body=False)

    assert model.ready
    assert model.current_state["Status"] == "complete"
    assert model.state_columns == ["ID", "Status", "Task", "Evidence", "Notes"]
    assert model.state_rows[1]["Status"] == "deferred"


def test_execute_from_tracking_issue_completion_gate_blocks_in_progress_status() -> None:
    module = _load_module()
    payload = _completion_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, repair_body=False)

    assert model.ready
    assert not model.completion.ready
    assert "state status is in-progress; expected complete" in model.completion.blockers


def test_execute_from_tracking_issue_completion_gate_blocks_pending_task_row() -> None:
    module = _load_module()
    payload = _completion_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, repair_body=False)

    assert model.ready
    assert not model.completion.ready
    assert model.completion.incomplete_rows == [
        {
            "ID": "1.2",
            "Status": "pending",
            "Task": "Implement lifecycle issue parsing",
            "Evidence": "helper module",
            "Notes": "Add parser and report schema.",
        }
    ]
    assert "incomplete task rows: 1.2=pending" in model.completion.blockers


def test_execute_from_tracking_issue_lifecycle_blocks_noncanonical_state_table() -> None:
    module = _load_module()
    payload = _issue_payload()
    payload["comments"][2]["body"] = _state_body(table_header="| ID | Status | Task | Notes |")

    issue = module.issue_from_payload(payload)
    model = module.analyze_issue(issue, repair_body=False)

    assert not model.ready
    failed = {check.name: check.detail for check in model.checks if check.status == "fail"}
    assert failed["state schema"] == "state table columns drift: ID, Status, Task, Notes"


def test_execute_from_tracking_issue_lifecycle_blocks_missing_snapshots() -> None:
    module = _load_module()
    payload = _issue_payload()
    payload["comments"] = payload["comments"][2:]

    issue = module.issue_from_payload(payload)
    model = module.analyze_issue(issue, repair_body=False)

    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_execute_from_tracking_issue_lifecycle_script_dry_run_writes_outputs(tmp_path: Path) -> None:
    issue_json = tmp_path / "issue.json"
    body_out = tmp_path / "body.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-lifecycle.sh"),
            "--issue-json",
            str(issue_json),
            "--dry-run",
            "--body-out",
            str(body_out),
            "--report-out",
            str(report_out),
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    assert proc.returncode == 0, proc.stderr
    assert "status=audit-dry-run" in proc.stdout
    assert "dashboard_drift=true" in proc.stdout
    assert "## Current Dashboard" in body_out.read_text("utf-8")
    report = json.loads(report_out.read_text("utf-8"))
    assert report["ready"] is True
    assert report["repair_body"] is False
    assert report["dashboard_drift"] is True


def test_execute_from_tracking_issue_lifecycle_repair_body_dry_run(tmp_path: Path) -> None:
    issue_json = tmp_path / "issue.json"
    body_out = tmp_path / "body.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-lifecycle.sh"),
            "--issue-json",
            str(issue_json),
            "--dry-run",
            "--repair-body",
            "--body-out",
            str(body_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 "repair_body=true" in proc.stdout
    assert "Latest validation" in body_out.read_text("utf-8")
    report = json.loads(report_out.read_text("utf-8"))
    assert report["repair_body"] is True
    assert report["dashboard_drift"] is True


def test_execute_from_tracking_issue_lifecycle_require_complete_blocks_incomplete_state(tmp_path: Path) -> None:
    issue_json = tmp_path / "issue.json"
    body_out = tmp_path / "body.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-lifecycle.sh"),
            "--issue-json",
            str(issue_json),
            "--dry-run",
            "--require-complete",
            "--body-out",
            str(body_out),
            "--report-out",
            str(report_out),
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    assert proc.returncode == 1
    assert "completion readiness" in proc.stderr
    report = json.loads(report_out.read_text("utf-8"))
    assert report["ready"] is True
    assert report["completion_ready"] is False
