from __future__ import annotations

import os
import subprocess
from pathlib import Path


def _write_minimal_ph_root(ph_root: Path, *, validation_rules: str = "{}") -> None:
    config = ph_root / ".project-handbook" / "config.json"
    config.parent.mkdir(parents=True, exist_ok=True)
    config.write_text(
        '{\n  "handbook_schema_version": 1,\n  "requires_ph_version": ">=0.0.1,<0.1.0",\n  "repo_root": "."\n}\n',
        encoding="utf-8",
    )

    ph_data_root = config.parent
    (ph_data_root / "process" / "checks").mkdir(parents=True, exist_ok=True)
    (ph_data_root / "process" / "automation").mkdir(parents=True, exist_ok=True)
    (ph_data_root / "process" / "sessions" / "templates").mkdir(parents=True, exist_ok=True)

    (ph_data_root / "process" / "checks" / "validation_rules.json").write_text(validation_rules, encoding="utf-8")
    (ph_data_root / "process" / "automation" / "system_scope_config.json").write_text(
        '{"routing_rules": {}}', encoding="utf-8"
    )
    (ph_data_root / "process" / "automation" / "reset_spec.json").write_text("{}", encoding="utf-8")


def test_sprint_plan_creates_skeleton_and_prints_project_hints(tmp_path: Path) -> None:
    _write_minimal_ph_root(tmp_path)
    env = dict(os.environ)

    result = subprocess.run(
        ["ph", "--root", str(tmp_path), "--no-post-hook", "sprint", "plan", "--sprint", "SPRINT-2099-01-01"],
        capture_output=True,
        text=True,
        env=env,
    )
    assert result.returncode == 0
    sprint_dir = tmp_path / ".project-handbook" / "sprints" / "2099" / "SPRINT-2099-01-01"
    plan_file = sprint_dir / "plan.md"
    gate_dir = sprint_dir / "tasks" / "TASK-001-sprint-gate-sprint-2099-01-01"
    assert result.stdout.splitlines() == [
        f"Created sprint plan: {plan_file}",
        "✅ Sprint gate task scaffolded (must exist Day 0; expected to close last).",
        f"  - Fill exit criteria + evidence in: {gate_dir / 'validation.md'}",
        "  - Pre-exec lint will fail until Sprint Goal/Exit criteria/evidence are explicit.",
        "Sprint structure seeded:",
        f"  📁 {sprint_dir}/",
        f"  📁 {sprint_dir}/tasks/ (ready for task creation)",
        "Next steps:",
        "  1. Edit plan.md with goals, lanes, and integration tasks",
        "  2. Create tasks via `ph task create ...`",
        "  3. Review `status/current_summary.md` after generating status",
        "  4. Re-run `ph onboarding session sprint-planning` for facilitation tips",
        "Sprint scaffold ready:",
        "  1. Edit .project-handbook/sprints/current/plan.md with goals, lanes, and integration tasks",
        "  2. Seed tasks via 'ph task create --title ... --feature ... --decision ADR-###'",
        "  3. Re-run 'ph sprint status' to confirm health + next-up ordering",
        "  4. Run 'ph validate --quick' before handing off to another agent",
        "  5. Need facilitation tips? 'ph onboarding session sprint-planning'",
    ]
    assert sprint_dir.exists()
    assert (sprint_dir / "tasks").exists()
    assert (sprint_dir / "plan.md").exists()
    assert (tmp_path / ".project-handbook" / "sprints" / "current").resolve() == sprint_dir.resolve()


def test_sprint_plan_bounded_template_matches_legacy(tmp_path: Path) -> None:
    _write_minimal_ph_root(
        tmp_path,
        validation_rules='{"sprint_management": {"mode": "bounded"}}\n',
    )
    env = dict(os.environ)
    env["PH_FAKE_TODAY"] = "2099-01-01"

    result = subprocess.run(
        ["ph", "--root", str(tmp_path), "--no-post-hook", "sprint", "plan", "--sprint", "SPRINT-2099-01-01"],
        capture_output=True,
        text=True,
        env=env,
    )
    assert result.returncode == 0

    plan_path = tmp_path / ".project-handbook" / "sprints" / "2099" / "SPRINT-2099-01-01" / "plan.md"
    assert plan_path.exists()

    expected_lines = [
        "---",
        "title: Sprint Plan - SPRINT-2099-01-01",
        "type: sprint-plan",
        "date: 2099-01-01",
        "sprint: SPRINT-2099-01-01",
        "mode: bounded",
        "tags: [sprint, planning]",
        "release: null",
        "---",
        "",
        "# Sprint Plan: SPRINT-2099-01-01",
        "",
        "## Sprint Model",
        (
            "This sprint uses **bounded planning** (ADR-0013): sprint scope is defined by "
            "*work boundaries* and *parallel lanes*, not a fixed calendar window or points cap."
        ),
        "",
        "## Sprint Goal",
        "1. [ ] Primary outcome (clear deliverable + validation gate)",
        "2. [ ] Secondary outcome",
        "3. [ ] Integration outcome (if multiple lanes)",
        "",
        "## Release Alignment (Explicit)",
        "If you have an active release, it is a *measurement context* — not an automatic scope cap.",
        "",
        "| Bucket | Intention | Notes |",
        "|--------|-----------|-------|",
        "| Release-critical | Work that must move critical-path features | |",
        "| Release-support | Work that enables the release but lives outside assigned features | |",
        "| Non-release | Necessary work that does not serve the active release | |",
        "",
        "## Boundaries (Lanes)",
        "Define lanes to maximize parallel execution and minimize cross-lane coupling.",
        "",
        "| Lane | Scope | Success Output |",
        "|------|-------|----------------|",
        "| `service/<name>` | | |",
        "| `infra/<name>` | | |",
        "| `docs/<area>` | | |",
        "| `integration/<scope>` | | |",
        "",
        "## Integration Tasks",
        "- List explicit cross-lane integration tasks and their dependencies.",
        "",
        "## Task Creation Guide",
        "```bash",
        (
            'ph task create --title "Task Name" --feature feature-name --decision ADR-XXX --points 3 '
            '--lane "ops/automation" --release current'
        ),
        (
            'ph task create --title "Gate: <name>" --feature feature-name --decision ADR-XXX --points 3 '
            '--lane "integration/<scope>" --release current --gate'
        ),
        "```",
        "",
        "## Telemetry (Points)",
        "- Story points are tracked for throughput/velocity trends, not for limiting sprint scope.",
        "",
        "## Dependencies & Risks",
        "- External dependencies",
        "- Cross-lane dependencies (integration risk)",
        "- Unknowns / validation gaps",
        "",
        "## Success Criteria",
        "- [ ] Lanes are explicit and independently executable",
        "- [ ] Integration tasks are explicit and dependency-linked",
        "- [ ] All committed tasks completed (points recorded for telemetry)",
    ]
    expected = "\n".join(expected_lines) + "\n"

    assert plan_path.read_text(encoding="utf-8") == expected


def test_sprint_plan_scaffolds_release_slot_alignment_when_release_active(tmp_path: Path) -> None:
    _write_minimal_ph_root(
        tmp_path,
        validation_rules='{"sprint_management": {"mode": "bounded"}}\n',
    )
    env = dict(os.environ)
    env["PH_FAKE_TODAY"] = "2099-01-01"

    release = subprocess.run(
        [
            "ph",
            "--root",
            str(tmp_path),
            "--no-post-hook",
            "release",
            "plan",
            "--version",
            "v1.2.3",
            "--sprints",
            "2",
            "--activate",
        ],
        capture_output=True,
        text=True,
        env=env,
    )
    assert release.returncode == 0

    release_plan_path = tmp_path / ".project-handbook" / "releases" / "v1.2.3" / "plan.md"
    release_plan_text = release_plan_path.read_text(encoding="utf-8")
    release_lines = release_plan_text.splitlines()
    assert release_lines and release_lines[0].strip() == "---"
    end_front_matter = release_lines.index("---", 1)
    front_matter = "\n".join(release_lines[: end_front_matter + 1]) + "\n\n"

    release_plan_path.write_text(
        front_matter
        + "\n".join(
            [
                "# Release v1.2.3",
                "",
                "## Slot 1: Foundation",
                "### Slot Goal",
                "- Build login flow",
                "### Enablement",
                "- Unlock onboarding",
                "### Scope Boundaries",
                "In scope:",
                "- Basic auth",
                "Out of scope:",
                "- SSO",
                "### Intended Gates",
                "- Gate: Login smoke",
                "- Gate: SSO ready",
                "",
                "## Slot 2: Hardening",
                "### Slot Goal",
                "- Stabilize and polish",
                "### Enablement",
                "- Raise confidence",
                "### Scope Boundaries",
                "In scope:",
                "- Error states",
                "Out of scope:",
                "- New major features",
                "### Intended Gates",
                "- Gate: Demo-ready UX",
                "",
            ]
        )
        + "\n",
        encoding="utf-8",
    )

    (tmp_path / ".project-handbook" / "releases" / "v1.2.3" / "features.yaml").write_text(
        "\n".join(
            [
                "# Feature assignments for v1.2.3",
                "# Auto-managed by release commands",
                "",
                "version: v1.2.3",
                "timeline_mode: sprint_slots",
                "start_sprint_slot: 1",
                "end_sprint_slot: 2",
                "planned_sprints: 2",
                "",
                "features:",
                "  feature-a:",
                "    slot: 1",
                "    commitment: committed",
                "    intent: deliver",
                "    type: regular",
                "    priority: P1",
                "    status: planned",
                "    completion: 0",
                "    critical_path: False",
                "",
            ]
        )
        + "\n",
        encoding="utf-8",
    )

    sprint = subprocess.run(
        ["ph", "--root", str(tmp_path), "--no-post-hook", "sprint", "plan", "--sprint", "SPRINT-2099-01-01"],
        capture_output=True,
        text=True,
        env=env,
    )
    assert sprint.returncode == 0

    sprint_plan_path = tmp_path / ".project-handbook" / "sprints" / "2099" / "SPRINT-2099-01-01" / "plan.md"
    sprint_plan_text = sprint_plan_path.read_text(encoding="utf-8")
    assert "release: v1.2.3" in sprint_plan_text
    assert "release_sprint_slot: 1" in sprint_plan_text
    assert "## Release Alignment (Slot 1)" in sprint_plan_text
    assert "Slot goal: Build login flow" in sprint_plan_text
    assert "Enablement: Unlock onboarding" in sprint_plan_text
    assert "Intended gates:" in sprint_plan_text
    assert "- Gate: Login smoke" in sprint_plan_text
    assert "- Gate: SSO ready" in sprint_plan_text
    assert "Slot Features:" in sprint_plan_text
    assert "- feature-a [committed/deliver]" in sprint_plan_text


def test_sprint_plan_prints_pnpm_make_preamble_when_package_json_present(tmp_path: Path) -> None:
    _write_minimal_ph_root(tmp_path)
    (tmp_path / "package.json").write_text(
        '{\n  "name": "project-handbook",\n  "version": "0.0.0"\n}\n',
        encoding="utf-8",
    )
    env = dict(os.environ)

    result = subprocess.run(
        ["ph", "--root", str(tmp_path), "--no-post-hook", "sprint", "plan", "--sprint", "SPRINT-2099-01-01"],
        capture_output=True,
        text=True,
        env=env,
    )
    assert result.returncode == 0
    lines = result.stdout.splitlines()
    assert lines[0:4] == [
        "",
        f"> project-handbook@0.0.0 ph {tmp_path.resolve()}",
        "> ph sprint plan --sprint SPRINT-2099-01-01",
        "",
    ]


def test_sprint_open_prints_pnpm_make_preamble_when_package_json_present(tmp_path: Path) -> None:
    _write_minimal_ph_root(tmp_path)
    (tmp_path / "package.json").write_text(
        '{\n  "name": "project-handbook",\n  "version": "0.0.0"\n}\n',
        encoding="utf-8",
    )
    env = dict(os.environ)

    sprint_id = "SPRINT-2099-01-02"
    sprint_dir = tmp_path / ".project-handbook" / "sprints" / "2099" / sprint_id
    sprint_dir.mkdir(parents=True, exist_ok=True)

    result = subprocess.run(
        ["ph", "--root", str(tmp_path), "--no-post-hook", "sprint", "open", "--sprint", sprint_id],
        capture_output=True,
        text=True,
        env=env,
    )
    assert result.returncode == 0
    lines = result.stdout.splitlines()
    assert lines[0:4] == [
        "",
        f"> project-handbook@0.0.0 ph {tmp_path.resolve()}",
        f"> ph sprint open --sprint {sprint_id}",
        "",
    ]
    assert lines[4:] == [f"✅ Current sprint set to: {sprint_id}"]


def test_sprint_plan_and_open_work_in_system_scope(tmp_path: Path) -> None:
    _write_minimal_ph_root(tmp_path)
    env = dict(os.environ)

    planned = subprocess.run(
        [
            "ph",
            "--root",
            str(tmp_path),
            "--scope",
            "system",
            "--no-post-hook",
            "sprint",
            "plan",
            "--sprint",
            "SPRINT-2099-01-02",
        ],
        capture_output=True,
        text=True,
        env=env,
    )
    assert planned.returncode == 0
    assert planned.stdout.splitlines() == [
        "System-scope sprint scaffold ready:",
        "  1. Edit .project-handbook/system/sprints/current/plan.md with goals, lanes, and integration tasks",
        "  2. Seed tasks via 'ph --scope system task create --title ... --feature ... --decision ADR-###'",
        "  3. Re-run 'ph --scope system sprint status' to confirm health + next-up ordering",
        "  4. Run 'ph --scope system validate --quick' before handing off to another agent",
    ]

    sprint_dir = tmp_path / ".project-handbook" / "system" / "sprints" / "2099" / "SPRINT-2099-01-02"
    plan_md = sprint_dir / "plan.md"
    assert plan_md.exists()
    text = plan_md.read_text(encoding="utf-8")
    assert "release: null" in text
    assert "Release Context" not in text

    opened = subprocess.run(
        [
            "ph",
            "--root",
            str(tmp_path),
            "--scope",
            "system",
            "--no-post-hook",
            "sprint",
            "open",
            "--sprint",
            "SPRINT-2099-01-02",
        ],
        capture_output=True,
        text=True,
        env=env,
    )
    assert opened.returncode == 0
    assert opened.stdout.strip() == "✅ Current sprint set to: SPRINT-2099-01-02"
    assert (tmp_path / ".project-handbook" / "system" / "sprints" / "current").resolve() == sprint_dir.resolve()


def test_sprint_open_updates_current_symlink_and_prints_exact_stdout(tmp_path: Path) -> None:
    _write_minimal_ph_root(tmp_path)
    env = dict(os.environ)

    sprint_id = "SPRINT-2099-01-02"
    sprint_dir = tmp_path / ".project-handbook" / "sprints" / "2099" / sprint_id
    sprint_dir.mkdir(parents=True, exist_ok=True)

    opened = subprocess.run(
        ["ph", "--root", str(tmp_path), "--no-post-hook", "sprint", "open", "--sprint", sprint_id],
        capture_output=True,
        text=True,
        env=env,
    )
    assert opened.returncode == 0
    assert opened.stdout == f"✅ Current sprint set to: {sprint_id}\n"

    current_link = tmp_path / ".project-handbook" / "sprints" / "current"
    assert current_link.exists()
    assert current_link.resolve() == sprint_dir.resolve()

    if current_link.is_symlink():
        assert current_link.readlink() == Path("2099") / sprint_id
