from __future__ import annotations

import subprocess
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 _script_path() -> Path:
    return _skill_root() / "scripts" / "plan-tracking-issue.sh"


def _git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
    return subprocess.run(["git", *args], cwd=repo, text=True, capture_output=True, check=False)


def _assert_ok(proc: subprocess.CompletedProcess[str]) -> None:
    assert proc.returncode == 0, proc.stderr


def _init_repo(repo: Path) -> None:
    _assert_ok(_git(repo, "init", "-q"))
    _assert_ok(_git(repo, "checkout", "-q", "-B", "main"))
    _assert_ok(_git(repo, "config", "user.email", "test@example.com"))
    _assert_ok(_git(repo, "config", "user.name", "Test User"))


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


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


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

    assert "The issue is a tracker, not a replacement source artifact" in text
    assert "GitHub: `gh issue create`" in text
    assert "GitLab: `glab issue create`" in text
    assert "`create-plan` still does not open live provider issues by default" in text
    assert "`dispatch-plan-delivery`" in text


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

    assert "Bootstrap-from-discussion mode" in text
    assert "run `discussion-to-implementation-doc` first, then `create-plan`" in text
    assert "Use `discussion-to-implementation-doc` to save the implementation-readiness source artifact" in text
    assert "Use `create-plan` to save `docs/plans/<slug>/<slug>-plan.md`" in text
    assert "Existing-plan mode does not create another plan" in text
    assert "still requires `--plan <path>`" in text
    assert "No execution-state file is created merely for issue tracking" in text


def test_plan_tracking_issue_contract_snapshots_for_issue_backed_execution() -> 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 "Snapshot comments are workflow actions after the issue URL exists" in text
    assert "renderable GitHub Markdown" in text
    assert "GitHub `<details>` blocks" in text
    assert "marker lines and metadata stay outside" in text
    assert "content in outer `markdown` / `md` code fences" in text
    assert "`execute-from-tracking-issue`" in text
    assert "snapshot comment status" in text


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

    assert "Live provider issue creation requires committed local plan and source files" in text
    assert "Staged, unstaged, or untracked plan/source files are not" in text
    assert "enough for live issue creation" in text
    assert "use `--dry-run` while drafting" in text


def test_plan_tracking_issue_script_renders_github_dry_run_body(tmp_path: Path) -> None:
    plan = tmp_path / "demo-plan.md"
    source = tmp_path / "demo-discussion-source.md"
    body_out = tmp_path / "body.md"
    source.write_text("# Demo Source\n", encoding="utf-8")
    plan.write_text(
        "\n".join(
            [
                "# Plan: `Demo Tracker`",
                "",
                "## Read First",
                "",
                f"- Primary source: `{source.as_posix()}`",
                "- Source type: discussion-to-implementation-doc",
            ]
        )
        + "\n",
        encoding="utf-8",
    )

    result = subprocess.run(
        [
            str(_script_path()),
            "--plan",
            str(plan),
            "--provider",
            "github",
            "--dry-run",
            "--label",
            "plan",
            "--body-out",
            str(body_out),
        ],
        check=True,
        text=True,
        capture_output=True,
    )

    assert "DRY-RUN-TRACKING-ISSUE-URL" in result.stdout
    assert "provider=github" in result.stdout
    assert "title=Track plan: Demo Tracker" in result.stdout
    assert "gh issue create" in result.stderr

    body = body_out.read_text(encoding="utf-8")
    assert "The plan bundle remains the source of truth" in body
    assert f"- Primary source: `{source.as_posix()}`" in body
    assert f"- Plan: `{plan.as_posix()}`" in body
    assert "Execution state:" in body


def test_plan_tracking_issue_script_supports_gitlab_dry_run_metadata(tmp_path: Path) -> None:
    plan = tmp_path / "gitlab-plan.md"
    body_out = tmp_path / "body.md"
    plan.write_text(
        "\n".join(
            [
                "# Plan: `GitLab Tracker`",
                "",
                "## Read First",
                "",
                "- Primary source: `docs/plans/gitlab/gitlab-review-source.md`",
                "- Source type: review-to-improvement-doc",
            ]
        )
        + "\n",
        encoding="utf-8",
    )

    result = subprocess.run(
        [
            str(_script_path()),
            "--plan",
            str(plan),
            "--provider",
            "gitlab",
            "--dry-run",
            "--due-date",
            "2026-06-01",
            "--weight",
            "2",
            "--body-out",
            str(body_out),
        ],
        check=True,
        text=True,
        capture_output=True,
    )

    assert "DRY-RUN-TRACKING-ISSUE-URL" in result.stdout
    assert "provider=gitlab" in result.stdout
    assert "glab issue create" in result.stderr
    assert "--due-date 2026-06-01" in result.stderr
    assert "--weight 2" in result.stderr
    assert "Provider: gitlab" in body_out.read_text(encoding="utf-8")


def test_plan_tracking_issue_script_rejects_provider_specific_flags(tmp_path: Path) -> None:
    plan = tmp_path / "demo-plan.md"
    plan.write_text(
        "\n".join(
            [
                "# Plan: `Demo Tracker`",
                "",
                "## Read First",
                "",
                "- Primary source: `docs/plans/demo/demo-review-source.md`",
            ]
        )
        + "\n",
        encoding="utf-8",
    )

    result = subprocess.run(
        [
            str(_script_path()),
            "--plan",
            str(plan),
            "--provider",
            "gitlab",
            "--project",
            "Roadmap",
            "--dry-run",
        ],
        text=True,
        capture_output=True,
    )

    assert result.returncode == 2
    assert "--project is GitHub-only" in result.stderr


def test_plan_tracking_issue_live_rejects_uncommitted_plan_before_provider_call(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    repo.mkdir()
    _init_repo(repo)
    plan_dir = repo / "docs" / "plans" / "demo"
    plan_dir.mkdir(parents=True)
    source = plan_dir / "demo-discussion-source.md"
    plan = plan_dir / "demo-plan.md"
    source.write_text("# Demo Source\n", encoding="utf-8")
    plan.write_text(
        "\n".join(
            [
                "# Plan: `Demo Tracker`",
                "",
                "## Read First",
                "",
                "- Primary source: `docs/plans/demo/demo-discussion-source.md`",
                "- Source type: discussion-to-implementation-doc",
            ]
        )
        + "\n",
        encoding="utf-8",
    )
    _assert_ok(_git(repo, "add", "docs/plans/demo/demo-discussion-source.md"))
    _assert_ok(_git(repo, "commit", "-q", "-m", "docs: add source"))

    result = subprocess.run(
        [
            str(_script_path()),
            "--plan",
            "docs/plans/demo/demo-plan.md",
            "--provider",
            "github",
        ],
        cwd=repo,
        text=True,
        capture_output=True,
    )

    assert result.returncode == 1
    assert "live issue creation requires committed local files" in result.stderr
    assert "plan file is not committed: docs/plans/demo/demo-plan.md" in result.stderr
    assert "Commit it before creating a live tracking issue" in result.stderr


def test_plan_tracking_issue_live_rejects_modified_source_before_provider_call(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    repo.mkdir()
    _init_repo(repo)
    plan_dir = repo / "docs" / "plans" / "demo"
    plan_dir.mkdir(parents=True)
    source = plan_dir / "demo-discussion-source.md"
    plan = plan_dir / "demo-plan.md"
    source.write_text("# Demo Source\n", encoding="utf-8")
    plan.write_text(
        "\n".join(
            [
                "# Plan: `Demo Tracker`",
                "",
                "## Read First",
                "",
                "- Primary source: `docs/plans/demo/demo-discussion-source.md`",
                "- Source type: discussion-to-implementation-doc",
            ]
        )
        + "\n",
        encoding="utf-8",
    )
    _assert_ok(_git(repo, "add", "docs/plans/demo/demo-discussion-source.md", "docs/plans/demo/demo-plan.md"))
    _assert_ok(_git(repo, "commit", "-q", "-m", "docs: add plan bundle"))
    source.write_text("# Demo Source\n\nChanged draft.\n", encoding="utf-8")

    result = subprocess.run(
        [
            str(_script_path()),
            "--plan",
            "docs/plans/demo/demo-plan.md",
            "--provider",
            "github",
        ],
        cwd=repo,
        text=True,
        capture_output=True,
    )

    assert result.returncode == 1
    assert "primary source file has unstaged changes: docs/plans/demo/demo-discussion-source.md" in result.stderr
