"""Tests for architect runner lifecycle."""

from __future__ import annotations

import json
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from cleave.architect.config import ArchitectConfig
from cleave.architect.dag import DAGStore, Phase
from cleave.architect.runner import (
    ArchitectResult,
    _build_orchestrator_config,
    _generate_db_path,
    _populate_dag,
    execute_sync,
    plan_sync,
    resume_sync,
)
from cleave.cli import _format_architect_result


def _make_config(tmp_path: Path, **kwargs) -> ArchitectConfig:
    (tmp_path / ".git").mkdir(exist_ok=True)
    defaults = {
        "directive": "Build auth system",
        "repo_path": tmp_path,
    }
    defaults.update(kwargs)
    return ArchitectConfig(**defaults)


class TestGenerateDbPath:
    def test_creates_directory(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path)
        db_path = _generate_db_path(config)
        assert db_path.parent.exists()
        assert db_path.name == "architect.db"
        assert ".architect-" in str(db_path.parent.name)

    def test_slugifies_directive(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, directive="Add JWT Auth!")
        db_path = _generate_db_path(config)
        assert "add-jwt-auth" in str(db_path.parent.name)


class TestBuildOrchestratorConfig:
    def test_basic_mapping(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, phase_budget_usd=75.0, executor_model="sonnet")
        phase = Phase(id="p1", label="Test", description="Do the thing")

        orch = _build_orchestrator_config(phase, config)
        assert orch.directive == "Do the thing"
        assert orch.repo_path == tmp_path
        assert orch.max_budget_usd == 75.0
        assert orch.model == "sonnet"

    def test_config_json_overrides(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, success_criteria=["default criterion"])
        phase = Phase(
            id="p1", label="Test", description="Do the thing",
            config_json=json.dumps({"success_criteria": ["custom criterion"]}),
        )

        orch = _build_orchestrator_config(phase, config)
        assert orch.success_criteria == ["custom criterion"]

    def test_invalid_config_json_ignored(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path)
        phase = Phase(
            id="p1", label="Test", description="Do it",
            config_json="not valid json",
        )

        orch = _build_orchestrator_config(phase, config)
        assert orch.directive == "Do it"  # Should not raise


class TestPopulateDag:
    def test_populates_phases(self, tmp_path: Path) -> None:
        with DAGStore(tmp_path / "test.db") as dag:
            plan_data = {
                "phases": [
                    {"id": "p1", "label": "Phase 1", "description": "Do first"},
                    {"id": "p2", "label": "Phase 2", "description": "Do second", "depends_on": ["p1"]},
                ],
                "rationale": "Sequential",
            }
            _populate_dag(dag, plan_data)

            phases = dag.list_phases()
            assert len(phases) == 2
            assert dag.get_dependencies("p2") == ["p1"]

    def test_assigns_waves(self, tmp_path: Path) -> None:
        with DAGStore(tmp_path / "test.db") as dag:
            plan_data = {
                "phases": [
                    {"id": "p1", "label": "Phase 1", "description": "Do first"},
                    {"id": "p2", "label": "Phase 2", "description": "Do second", "depends_on": ["p1"]},
                    {"id": "p3", "label": "Phase 3", "description": "Do third", "depends_on": ["p2"]},
                ],
            }
            _populate_dag(dag, plan_data)

            p1 = dag.get_phase("p1")
            p2 = dag.get_phase("p2")
            p3 = dag.get_phase("p3")
            assert p1.wave == 0
            assert p2.wave == 1
            assert p3.wave == 2

    def test_stores_success_criteria(self, tmp_path: Path) -> None:
        with DAGStore(tmp_path / "test.db") as dag:
            plan_data = {
                "phases": [
                    {"id": "p1", "label": "Phase 1", "description": "Do it",
                     "success_criteria": ["tests pass"]},
                    {"id": "p2", "label": "Phase 2", "description": "Also do it"},
                ],
            }
            _populate_dag(dag, plan_data)

            p1 = dag.get_phase("p1")
            assert p1.config_json != ""
            config = json.loads(p1.config_json)
            assert config["success_criteria"] == ["tests pass"]


class TestArchitectResult:
    def test_basic_fields(self) -> None:
        result = ArchitectResult(
            success=True,
            db_path=Path("/tmp/test.db"),
            phases_completed=3,
            phases_total=5,
        )
        assert result.success is True
        assert result.paused is False
        assert result.error is None

    def test_format_architect_result_complete(self, tmp_path: Path) -> None:
        result = ArchitectResult(
            success=True,
            db_path=tmp_path / "architect.db",
            report_path=tmp_path / "report.md",
            total_cost_usd=42.50,
            phases_completed=4,
            phases_total=4,
        )
        output = _format_architect_result(result)
        assert output["success"] is True
        assert output["paused"] is False
        assert output["total_cost_usd"] == 42.50
        assert output["phases_completed"] == 4
        assert output["phases_total"] == 4
        assert output["resume_command"] is None

    def test_format_architect_result_paused(self, tmp_path: Path) -> None:
        db_path = tmp_path / "architect.db"
        result = ArchitectResult(
            success=True,
            db_path=db_path,
            paused=True,
            phases_completed=2,
            phases_total=5,
        )
        output = _format_architect_result(result)
        assert output["paused"] is True
        assert output["resume_command"] == f"cleave architect --resume {db_path}"


class TestPlanSync:
    @patch("cleave.architect.runner.plan_phases")
    @patch("shutil.which", return_value="/usr/bin/claude")
    def test_plan_only(self, mock_which, mock_plan, tmp_path: Path) -> None:
        mock_plan.return_value = {
            "phases": [
                {"id": "p1", "label": "Phase 1", "description": "Do first"},
                {"id": "p2", "label": "Phase 2", "description": "Do second", "depends_on": ["p1"]},
            ],
            "rationale": "Sequential",
        }

        config = _make_config(tmp_path)
        result = plan_sync(config)
        assert result.success is True
        assert result.paused is True
        assert result.phases_total == 2
        assert result.db_path.exists()
