from __future__ import annotations

import json
from pathlib import Path

import pytest
from click.testing import CliRunner

from deskclaw.cli.main import cli


@pytest.fixture()
def runner() -> CliRunner:
    return CliRunner()


@pytest.fixture()
def plugin_json(tmp_path: Path) -> Path:
    cfg = tmp_path / "plugin.json"
    cfg.write_text(
        json.dumps(
            {
                "metadata": {"name": "cli-test", "version": "0.5.0"},
                "agent": {"default": "self", "config": {"self": {}}},
                "backup": {
                    "paths": [],
                    "exclude": [".git"],
                    "snapshot_dir": str(tmp_path / "snapshots"),
                },
                "execution": {
                    "benchmarks": [{"name": "stress-tests", "type": "custom"}],
                    "parallel": {"max_workers": 2},
                    "mock": {"enabled": False},
                },
                "evaluation": {},
                "output": {"results_dir": str(tmp_path / "results")},
            }
        )
    )
    return cfg


@pytest.fixture()
def plugin_with_backup(tmp_path: Path) -> Path:
    src = tmp_path / "agent_data"
    src.mkdir()
    (src / "config.yaml").write_text("key: value")

    cfg = tmp_path / "plugin.json"
    cfg.write_text(
        json.dumps(
            {
                "metadata": {"name": "backup-test"},
                "agent": {"default": "self", "config": {"self": {}}},
                "backup": {
                    "paths": [str(src)],
                    "exclude": [".git"],
                    "snapshot_dir": str(tmp_path / "snapshots"),
                },
                "execution": {
                    "benchmarks": [],
                    "parallel": {"max_workers": 1},
                    "mock": {"enabled": False},
                },
                "evaluation": {},
                "output": {"results_dir": str(tmp_path / "results")},
            }
        )
    )
    return cfg


class TestRootGroup:
    def test_help(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["--help"])
        assert result.exit_code == 0
        assert "DeskClaw" in result.output
        assert "backup" in result.output
        assert "setup" in result.output
        assert "run" in result.output
        assert "eval" in result.output
        assert "version" in result.output

    def test_version_flag(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["--version"])
        assert result.exit_code == 0
        assert "0.5.0" in result.output

    def test_version_command(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["version"])
        assert result.exit_code == 0
        assert "deskclaw 0.5.0" in result.output


class TestBackupCommands:
    def test_backup_help(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["backup", "--help"])
        assert result.exit_code == 0
        assert "create" in result.output
        assert "list" in result.output
        assert "restore" in result.output
        assert "prune" in result.output

    def test_backup_list_empty(self, runner: CliRunner, tmp_path: Path) -> None:
        snap_dir = str(tmp_path / "snapshots")
        result = runner.invoke(cli, ["backup", "list", "--snapshot-dir", snap_dir])
        assert result.exit_code == 0
        assert "No snapshots found" in result.output

    def test_backup_create_and_list(
        self, runner: CliRunner, plugin_with_backup: Path, tmp_path: Path
    ) -> None:
        snap_dir = str(tmp_path / "snapshots")

        result = runner.invoke(
            cli,
            [
                "backup",
                "create",
                "--agent",
                "test-agent",
                "--config",
                str(plugin_with_backup),
                "--snapshot-dir",
                snap_dir,
            ],
        )
        assert result.exit_code == 0
        assert "Snapshot created" in result.output

        result = runner.invoke(cli, ["backup", "list", "--snapshot-dir", snap_dir])
        assert result.exit_code == 0
        assert "test-agent" in result.output

    def test_backup_prune_empty(self, runner: CliRunner, tmp_path: Path) -> None:
        snap_dir = str(tmp_path / "snapshots")
        result = runner.invoke(
            cli, ["backup", "prune", "--keep", "5", "--snapshot-dir", snap_dir]
        )
        assert result.exit_code == 0
        assert "Nothing to prune" in result.output

    def test_backup_restore_requires_args(
        self, runner: CliRunner, tmp_path: Path
    ) -> None:
        snap_dir = str(tmp_path / "snapshots")
        result = runner.invoke(cli, ["backup", "restore", "--snapshot-dir", snap_dir])
        assert result.exit_code != 0


class TestSetupCommand:
    def test_setup_help(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["setup", "--help"])
        assert result.exit_code == 0
        assert "--agent" in result.output
        assert "--config" in result.output

    def test_setup_with_self_adapter(
        self, runner: CliRunner, plugin_json: Path, tmp_path: Path
    ) -> None:
        import deskclaw.adapters.nanoclaw  # noqa: F401

        result = runner.invoke(
            cli,
            [
                "setup",
                "--agent",
                "self",
                "--config",
                str(plugin_json),
                "--results-dir",
                str(tmp_path / "results"),
            ],
        )
        assert result.exit_code == 0
        assert "Setup complete" in result.output
        assert "self" in result.output

    def test_setup_missing_config(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["setup", "--config", "/nonexistent/plugin.yaml"])
        assert result.exit_code != 0


class TestRunCommands:
    def test_run_help(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["run", "--help"])
        assert result.exit_code == 0
        assert "full" in result.output
        assert "stress" in result.output
        assert "benchmark" in result.output
        assert "single" in result.output

    def test_run_full_pipeline(
        self, runner: CliRunner, plugin_json: Path, tmp_path: Path
    ) -> None:
        import deskclaw.adapters.nanoclaw  # noqa: F401

        result = runner.invoke(
            cli,
            [
                "run",
                "full",
                "--agent",
                "self",
                "--config",
                str(plugin_json),
                "--results-dir",
                str(tmp_path / "results"),
            ],
        )
        assert result.exit_code == 0
        assert "Stage 1: Backup" in result.output
        assert "Stage 2: Setup" in result.output
        assert "Stage 3: Execution" in result.output
        assert "Stage 4: Evaluation" in result.output
        assert "Pipeline Complete" in result.output

    def test_run_full_requires_agent(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["run", "full"])
        assert result.exit_code != 0
        assert "agent" in result.output.lower() or "agent" in result.stderr.lower()

    def test_run_stress(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["run", "stress", "--agent", "self"])
        assert result.exit_code == 0
        assert "ST-01" in result.output

    def test_run_stress_specific_tests(self, runner: CliRunner) -> None:
        result = runner.invoke(
            cli, ["run", "stress", "--agent", "self", "--test", "ST-01,ST-04"]
        )
        assert result.exit_code == 0
        assert "2 test(s)" in result.output

    def test_run_benchmark(self, runner: CliRunner) -> None:
        result = runner.invoke(
            cli, ["run", "benchmark", "--agent", "self", "--suite", "swe-bench"]
        )
        assert result.exit_code == 0
        assert "swe-bench" in result.output

    def test_run_benchmark_invalid_suite(self, runner: CliRunner) -> None:
        result = runner.invoke(
            cli, ["run", "benchmark", "--agent", "self", "--suite", "fake-suite"]
        )
        assert result.exit_code != 0

    def test_run_single(self, runner: CliRunner) -> None:
        result = runner.invoke(
            cli, ["run", "single", "--agent", "self", "--test", "ST-01"]
        )
        assert result.exit_code == 0
        assert "ST-01" in result.output


class TestEvalCommands:
    def test_eval_help(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["eval", "--help"])
        assert result.exit_code == 0
        assert "score" in result.output
        assert "report" in result.output
        assert "compare" in result.output

    def test_eval_score(self, runner: CliRunner, tmp_path: Path) -> None:
        results_dir = tmp_path / "results"
        results_dir.mkdir()
        result = runner.invoke(cli, ["eval", "score", "--results", str(results_dir)])
        assert result.exit_code == 0
        assert "Scoring" in result.output

    def test_eval_score_missing_dir(self, runner: CliRunner) -> None:
        result = runner.invoke(
            cli, ["eval", "score", "--results", "/nonexistent/results"]
        )
        assert result.exit_code != 0

    def test_eval_report(self, runner: CliRunner, tmp_path: Path) -> None:
        output_dir = str(tmp_path / "reports")
        result = runner.invoke(cli, ["eval", "report", "--output", output_dir])
        assert result.exit_code == 0
        assert "Report generated" in result.output
        assert Path(output_dir).exists()

    def test_eval_report_format_choices(
        self, runner: CliRunner, tmp_path: Path
    ) -> None:
        for fmt in ("json", "md", "both"):
            result = runner.invoke(
                cli,
                [
                    "eval",
                    "report",
                    "--format",
                    fmt,
                    "--output",
                    str(tmp_path / f"r_{fmt}"),
                ],
            )
            assert result.exit_code == 0

    def test_eval_compare(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["eval", "compare", "--agents", "agent-a,agent-b"])
        assert result.exit_code == 0
        assert "Comparing" in result.output

    def test_eval_compare_single_agent_fails(self, runner: CliRunner) -> None:
        result = runner.invoke(cli, ["eval", "compare", "--agents", "only-one"])
        assert result.exit_code != 0


class TestRunFullPipelineOrder:
    def test_pipeline_stages_in_order(
        self, runner: CliRunner, plugin_json: Path, tmp_path: Path
    ) -> None:
        import deskclaw.adapters.nanoclaw  # noqa: F401

        result = runner.invoke(
            cli,
            [
                "run",
                "full",
                "--agent",
                "self",
                "--config",
                str(plugin_json),
                "--results-dir",
                str(tmp_path / "results"),
            ],
        )
        assert result.exit_code == 0

        output = result.output
        idx_backup = output.index("Stage 1: Backup")
        idx_setup = output.index("Stage 2: Setup")
        idx_exec = output.index("Stage 3: Execution")
        idx_eval = output.index("Stage 4: Evaluation")

        assert idx_backup < idx_setup < idx_exec < idx_eval
