"""Tests for abreka bootstrap command."""

from __future__ import annotations

import json
import os
from pathlib import Path
from unittest.mock import patch

import pytest
from click.testing import CliRunner

from abreka.cli import main


class TestBootstrapScaffolding:
    """Test the mechanical setup phase (--no-agent skips pi)."""

    def test_creates_uv_project_and_abreka_structure(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()

        # Mock uv init to create the project structure manually
        def fake_uv_init(cmd, **kwargs):
            project_dir = tmp_path / "my-project"
            project_dir.mkdir()
            (project_dir / "pyproject.toml").write_text(
                '[project]\nname = "my-project"\nversion = "0.1.0"\n'
            )
            src = project_dir / "src" / "my_project"
            src.mkdir(parents=True)
            (src / "__init__.py").write_text("")
            return _ok_result()

        with patch("abreka.cli.bootstrap_cmd.subprocess.run", side_effect=fake_uv_init):
            result = runner.invoke(main, ["bootstrap", "my-project", "--no-agent"])

        assert result.exit_code == 0, result.output

        abreka_dir = tmp_path / "my-project" / "abreka"
        assert (abreka_dir / "config.toml").is_file()
        assert (abreka_dir / "goal.md").is_file()
        assert (abreka_dir / "index.json").is_file()
        assert (abreka_dir / "backlog_index.json").is_file()
        assert (abreka_dir / "experiments").is_dir()
        assert (abreka_dir / "prompts" / "propose_exploit.md").is_file()
        assert (abreka_dir / "prompts" / "implement.md").is_file()

        # Verify index is empty
        index = json.loads((abreka_dir / "index.json").read_text())
        assert index == {}

    def test_bootstrap_calls_uv_init_with_app_package_flags(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()
        init_calls: list[list[str]] = []

        def fake_uv_init(cmd, **kwargs):
            init_calls.append(cmd)
            project_dir = tmp_path / "my-app"
            project_dir.mkdir()
            (project_dir / "pyproject.toml").write_text(
                '[project]\nname = "my-app"\nversion = "0.1.0"\n'
            )
            src = project_dir / "src" / "my_app"
            src.mkdir(parents=True)
            (src / "__init__.py").write_text("")
            return _ok_result()

        with patch("abreka.cli.bootstrap_cmd.subprocess.run", side_effect=fake_uv_init):
            result = runner.invoke(main, ["bootstrap", "my-app", "--no-agent"])

        assert result.exit_code == 0, result.output
        assert init_calls == [["uv", "init", "--app", "--package", "my-app"]]

    def test_config_has_correct_project_name(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()

        def fake_uv_init(cmd, **kwargs):
            project_dir = tmp_path / "fashion-mnist"
            project_dir.mkdir()
            (project_dir / "pyproject.toml").write_text(
                '[project]\nname = "fashion-mnist"\nversion = "0.1.0"\n'
            )
            src = project_dir / "src" / "fashion_mnist"
            src.mkdir(parents=True)
            (src / "__init__.py").write_text("")
            return _ok_result()

        with patch("abreka.cli.bootstrap_cmd.subprocess.run", side_effect=fake_uv_init):
            result = runner.invoke(main, ["bootstrap", "fashion-mnist", "--no-agent"])

        assert result.exit_code == 0, result.output
        assert "fashion-mnist" in result.output

        import tomllib
        config_path = tmp_path / "fashion-mnist" / "abreka" / "config.toml"
        with open(config_path, "rb") as f:
            config = tomllib.load(f)
        assert config["project"]["name"] == "fashion-mnist"
        assert config["project"]["lib"] == "fashion_mnist"

    def test_prints_next_steps_with_no_agent(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()

        def fake_uv_init(cmd, **kwargs):
            project_dir = tmp_path / "test-proj"
            project_dir.mkdir()
            (project_dir / "pyproject.toml").write_text(
                '[project]\nname = "test-proj"\nversion = "0.1.0"\n'
            )
            src = project_dir / "src" / "test_proj"
            src.mkdir(parents=True)
            (src / "__init__.py").write_text("")
            return _ok_result()

        with patch("abreka.cli.bootstrap_cmd.subprocess.run", side_effect=fake_uv_init):
            result = runner.invoke(main, ["bootstrap", "test-proj", "--no-agent"])

        assert result.exit_code == 0, result.output
        assert "goal.md" in result.output


class TestBootstrapValidation:
    """Test error handling."""

    def test_fails_if_directory_exists(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        (tmp_path / "already-here").mkdir()
        runner = CliRunner()
        result = runner.invoke(main, ["bootstrap", "already-here", "--no-agent"])
        assert result.exit_code != 0
        assert "already exists" in result.output

    def test_fails_if_uv_not_found(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()
        with patch("shutil.which", return_value=None):
            result = runner.invoke(main, ["bootstrap", "test-proj", "--no-agent"])
        assert result.exit_code != 0
        assert "uv" in result.output.lower()

    def test_prompts_for_name_if_not_given(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()

        def fake_uv_init(cmd, **kwargs):
            project_dir = tmp_path / "prompted-name"
            project_dir.mkdir()
            (project_dir / "pyproject.toml").write_text(
                '[project]\nname = "prompted-name"\nversion = "0.1.0"\n'
            )
            src = project_dir / "src" / "prompted_name"
            src.mkdir(parents=True)
            (src / "__init__.py").write_text("")
            return _ok_result()

        with patch("abreka.cli.bootstrap_cmd.subprocess.run", side_effect=fake_uv_init):
            result = runner.invoke(
                main, ["bootstrap", "--no-agent"], input="prompted-name\n"
            )

        assert result.exit_code == 0, result.output
        assert (tmp_path / "prompted-name" / "abreka" / "config.toml").is_file()


class TestBootstrapPiLaunch:
    """Test that pi is launched correctly when --no-agent is not set."""

    def test_launches_pi_with_correct_args(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()

        def fake_uv_init(cmd, **kwargs):
            project_dir = tmp_path / "my-project"
            project_dir.mkdir()
            (project_dir / "pyproject.toml").write_text(
                '[project]\nname = "my-project"\nversion = "0.1.0"\n'
            )
            src = project_dir / "src" / "my_project"
            src.mkdir(parents=True)
            (src / "__init__.py").write_text("")
            return _ok_result()

        execvp_calls = []

        def fake_execvp(file, args):
            execvp_calls.append((file, args))
            # Raise SystemExit to stop execution after execvp
            raise SystemExit(0)

        with (
            patch("abreka.cli.bootstrap_cmd.subprocess.run", side_effect=fake_uv_init),
            patch("shutil.which", return_value="/usr/bin/pi"),
            patch("os.execvp", side_effect=fake_execvp),
            patch("os.chdir"),
        ):
            result = runner.invoke(main, ["bootstrap", "my-project"])

        assert len(execvp_calls) == 1
        file, args = execvp_calls[0]
        assert file == "pi"
        assert "--append-system-prompt" in args
        assert "--no-session" in args

    def test_fails_if_pi_not_found(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()

        def fake_uv_init(cmd, **kwargs):
            project_dir = tmp_path / "my-project"
            project_dir.mkdir()
            (project_dir / "pyproject.toml").write_text(
                '[project]\nname = "my-project"\nversion = "0.1.0"\n'
            )
            src = project_dir / "src" / "my_project"
            src.mkdir(parents=True)
            (src / "__init__.py").write_text("")
            return _ok_result()

        def which_no_pi(name):
            if name == "pi":
                return None
            return f"/usr/bin/{name}"

        with (
            patch("abreka.cli.bootstrap_cmd.subprocess.run", side_effect=fake_uv_init),
            patch("shutil.which", side_effect=which_no_pi),
        ):
            result = runner.invoke(main, ["bootstrap", "my-project"])

        assert result.exit_code != 0
        assert "pi" in result.output.lower()


def _ok_result():
    """Create a fake successful subprocess result."""
    from subprocess import CompletedProcess
    return CompletedProcess(args=[], returncode=0)
