import subprocess
import unittest
from pathlib import Path

from gzkit.cli import main
from tests.commands.common import CliRunner, _quick_init


class TestObpiValidateCommand(unittest.TestCase):
    def _git(self, *args: str) -> None:
        subprocess.run(["git", *args], check=True, capture_output=True, text=True)

    def _commit_all(self, message: str) -> None:
        self._git("add", "-A")
        status = subprocess.run(
            ["git", "status", "--porcelain"],
            check=True,
            capture_output=True,
            text=True,
        ).stdout.strip()
        if status:
            self._git("commit", "-m", message)

    def test_obpi_validate_prints_blockers_for_out_of_scope_changes(self) -> None:
        runner = CliRunner()
        with runner.isolated_filesystem():
            _quick_init()
            runner.invoke(main, ["plan", "create", "f", "--kind", "feature"])

            self._git("init")
            self._git("config", "user.email", "test@example.com")
            self._git("config", "user.name", "Test User")
            self._commit_all("chore: initial")

            obpi_path = Path("docs/design/adr/pre-release/ADR-0.1.0-f/obpis/OBPI-0.1.0-01-demo.md")
            obpi_path.parent.mkdir(parents=True, exist_ok=True)
            obpi_path.write_text(
                "---\n"
                "id: OBPI-0.1.0-01-demo\n"
                "parent: ADR-0.1.0-f\n"
                "item: 1\n"
                "lane: Lite\n"
                "status: Completed\n"
                "---\n\n"
                "# OBPI-0.1.0-01-demo\n\n"
                "## Allowed Paths\n"
                "- `docs/design/adr/pre-release/ADR-0.1.0-f/**` - in scope\n\n"
                "### Implementation Summary\n"
                "- Files created/modified: "
                "docs/design/adr/pre-release/ADR-0.1.0-f/obpis/OBPI-0.1.0-01-demo.md\n\n"
                "## Key Proof\n"
                "uv run gz adr status ADR-0.1.0-f --json\n",
                encoding="utf-8",
            )
            Path("docs/outside.md").parent.mkdir(parents=True, exist_ok=True)
            Path("docs/outside.md").write_text("outside\n", encoding="utf-8")

            result = runner.invoke(main, ["obpi", "validate", str(obpi_path)])

            self.assertEqual(result.exit_code, 1)
            self.assertIn("BLOCKERS:", result.output)
            self.assertIn("docs/outside.md", result.output)

    def test_obpi_validate_passes_for_allowlisted_changes(self) -> None:
        runner = CliRunner()
        with runner.isolated_filesystem():
            _quick_init()
            runner.invoke(main, ["plan", "create", "f", "--kind", "feature"])

            self._git("init")
            self._git("config", "user.email", "test@example.com")
            self._git("config", "user.name", "Test User")
            self._commit_all("chore: initial")

            obpi_path = Path("docs/design/adr/pre-release/ADR-0.1.0-f/obpis/OBPI-0.1.0-01-demo.md")
            obpi_path.parent.mkdir(parents=True, exist_ok=True)
            obpi_path.write_text(
                "---\n"
                "id: OBPI-0.1.0-01-demo\n"
                "parent: ADR-0.1.0-f\n"
                "item: 1\n"
                "lane: Lite\n"
                "status: Completed\n"
                "---\n\n"
                "# OBPI-0.1.0-01-demo\n\n"
                "## ADR Item\n"
                "- **Source ADR:** ADR-0.1.0-f\n"
                "- **Checklist Item:** #1\n\n"
                "## Objective\n"
                "Demo objective.\n\n"
                "## Lane\n"
                "**Lite**\n\n"
                "## Allowed Paths\n"
                "- `docs/design/adr/pre-release/ADR-0.1.0-f/**` - in scope\n"
                "- `src/**` - in scope\n\n"
                "## Denied Paths\n"
                "- None\n\n"
                "## Requirements (FAIL-CLOSED)\n"
                "1. MUST pass lint.\n\n"
                "## Quality Gates\n"
                "### Gate 1: ADR\n"
                "- [x] Intent recorded\n\n"
                "## Acceptance Criteria\n"
                "- [x] REQ-0.1.0-01-01: Demo passes\n\n"
                "### Implementation Summary\n"
                "- Files created/modified: src/demo.py\n\n"
                "### Key Proof\n"
                "uv run gz adr status ADR-0.1.0-f --json\n",
                encoding="utf-8",
            )
            Path("src").mkdir(exist_ok=True)
            Path("src/demo.py").write_text("print('ok')\n", encoding="utf-8")

            result = runner.invoke(main, ["obpi", "validate", str(obpi_path)])

            self.assertEqual(result.exit_code, 0)
            self.assertIn("OBPI Validation Passed", result.output)

    def test_obpi_validate_adr_flag_batch_validates(self) -> None:
        """#30: --adr flag validates all briefs under an ADR."""
        runner = CliRunner()
        with runner.isolated_filesystem():
            _quick_init()
            runner.invoke(main, ["plan", "create", "f", "--kind", "feature"])

            adr_dir = Path("design/adr/pre-release/ADR-0.1.0-f")
            obpi_dir = adr_dir / "obpis"
            obpi_dir.mkdir(parents=True, exist_ok=True)

            # One scaffold stub, one authored brief
            (obpi_dir / "OBPI-0.1.0-01-stub.md").write_text(
                "---\nid: OBPI-0.1.0-01-stub\nparent: ADR-0.1.0-f\n"
                "item: 1\nlane: Lite\nstatus: Draft\n---\n\n"
                "## Allowed Paths\n- `src/module/` - Reason\n\n"
                "## Requirements (FAIL-CLOSED)\n1. REQUIREMENT: First constraint\n",
                encoding="utf-8",
            )
            (obpi_dir / "OBPI-0.1.0-02-real.md").write_text(
                "---\nid: OBPI-0.1.0-02-real\nparent: ADR-0.1.0-f\n"
                "item: 2\nlane: Lite\nstatus: Draft\n---\n\n"
                "## ADR Item\n- **Source ADR:** ADR-0.1.0-f\n"
                "- **Checklist Item:** #2\n\n"
                "## Objective\nDefine port interfaces.\n\n"
                "## Lane\n**Lite**\n\n"
                "## Allowed Paths\n- `src/gzkit/ports/` - Port definitions\n\n"
                "## Denied Paths\n- None\n\n"
                "## Requirements (FAIL-CLOSED)\n"
                "1. MUST use typing.Protocol for ports.\n\n"
                "## Quality Gates\n### Gate 1: ADR\n- [ ] Intent recorded\n\n"
                "## Acceptance Criteria\n"
                "- [ ] REQ-0.1.0-02-01: Ports defined\n",
                encoding="utf-8",
            )

            result = runner.invoke(main, ["obpi", "validate", "--adr", "ADR-0.1.0-f"])

            self.assertEqual(result.exit_code, 1)
            self.assertIn("FAIL", result.output)
            self.assertIn("PASS", result.output)
            self.assertIn("1/2 briefs failed", result.output)

    def test_obpi_validate_authored_flag_blocks_thin_draft(self) -> None:
        runner = CliRunner()
        with runner.isolated_filesystem():
            _quick_init()

            obpi_path = Path("OBPI-0.1.0-01-demo.md")
            obpi_path.write_text(
                "---\n"
                "id: OBPI-0.1.0-01-demo\n"
                "parent: ADR-0.1.0-f\n"
                "item: 1\n"
                "lane: Lite\n"
                "status: Draft\n"
                "---\n\n"
                "# OBPI-0.1.0-01-demo\n\n"
                "## Allowed Paths\n- `src/gzkit/ports/` - in scope\n\n"
                "## Requirements (FAIL-CLOSED)\n1. REQUIREMENT: Use Protocols.\n\n"
                "## Acceptance Criteria\n"
                "- [ ] REQ-0.1.0-01-01: Ports are defined.\n",
                encoding="utf-8",
            )

            result = runner.invoke(main, ["obpi", "validate", str(obpi_path), "--authored"])

            self.assertEqual(result.exit_code, 1)
            self.assertIn("Objective", result.output)
            self.assertIn("Denied Paths", result.output)

    def test_obpi_validate_authored_flag_passes_substantive_draft(self) -> None:
        runner = CliRunner()
        with runner.isolated_filesystem():
            _quick_init()

            obpi_path = Path("OBPI-0.1.0-01-demo.md")
            obpi_path.write_text(
                "---\n"
                "id: OBPI-0.1.0-01-demo\n"
                "parent: ADR-0.1.0-f\n"
                "item: 1\n"
                "lane: Lite\n"
                "status: Draft\n"
                "---\n\n"
                "# OBPI-0.1.0-01-demo\n\n"
                "## ADR Item\n- **Source ADR:** ADR-0.1.0-f\n- **Checklist Item:** #1\n\n"
                "## Objective\nDefine typed port interfaces.\n\n"
                "## Lane\n**Lite** - Internal Python contract.\n\n"
                "## Allowed Paths\n- `src/gzkit/ports/` - in scope\n\n"
                "## Creates these files\n- `src/gzkit/ports/`\n\n"
                "## Denied Paths\n- `docs/user/manpages/` - docs stay unchanged\n\n"
                "## Requirements (FAIL-CLOSED)\n1. REQUIREMENT: Use Protocols.\n\n"
                "## Quality Gates\n### Gate 1: ADR\n- [ ] Intent recorded\n\n"
                "## Discovery Checklist\n"
                "**Prerequisites (check existence, STOP if missing):**\n"
                "- [ ] `src/gzkit/runtime.py` - runtime entry point\n\n"
                "**Existing Code (understand current state):**\n"
                "- [ ] `src/gzkit/ports.py` - current patterns\n\n"
                "## Verification\n```bash\n"
                "uv run gz validate --documents\n"
                "uv run gz lint\n"
                "uv run gz typecheck\n"
                "uv run gz test\n"
                "uv run -m unittest tests.test_ports\n"
                "```\n\n"
                "## Acceptance Criteria\n"
                "- [ ] REQ-0.1.0-01-01: Ports are defined.\n",
                encoding="utf-8",
            )

            result = runner.invoke(main, ["obpi", "validate", str(obpi_path), "--authored"])

            self.assertEqual(result.exit_code, 0, msg=result.output)
            self.assertIn("OBPI Validation Passed", result.output)

    def test_obpi_validate_no_args_shows_error(self) -> None:
        """Calling with neither path nor --adr shows an error."""
        runner = CliRunner()
        with runner.isolated_filesystem():
            _quick_init()
            result = runner.invoke(main, ["obpi", "validate"])
            self.assertEqual(result.exit_code, 1)
