import json
from pathlib import Path

import pytest
import yaml
from typer.testing import CliRunner

from codex_autorunner.cli import app
from codex_autorunner.core.config import (
    CONFIG_FILENAME,
    DEFAULT_HUB_CONFIG,
    load_hub_config,
)
from codex_autorunner.core.git_utils import run_git
from codex_autorunner.core.hub import HubSupervisor
from codex_autorunner.integrations.agents.wiring import (
    build_agent_backend_factory,
    build_app_server_supervisor_factory,
)
from tests.conftest import write_test_config


def _init_git_repo(path: Path) -> None:
    path.mkdir(parents=True, exist_ok=True)
    run_git(["init"], path, check=True)
    (path / "README.md").write_text("hello\n", encoding="utf-8")
    run_git(["add", "README.md"], path, check=True)
    run_git(
        [
            "-c",
            "user.name=Test",
            "-c",
            "user.email=test@example.com",
            "commit",
            "-m",
            "init",
        ],
        path,
        check=True,
    )


def test_hub_create_repo_cli(tmp_path: Path):
    hub_root = tmp_path / "hub"
    cfg = json.loads(json.dumps(DEFAULT_HUB_CONFIG))
    cfg["hub"]["repos_root"] = "workspace"
    write_test_config(hub_root / CONFIG_FILENAME, cfg)

    runner = CliRunner()
    result = runner.invoke(app, ["hub", "create", "demo", "--path", str(hub_root)])
    assert result.exit_code == 0

    repo_dir = hub_root / "workspace" / "demo"
    assert (repo_dir / ".git").exists()
    assert (repo_dir / ".codex-autorunner" / "state.sqlite3").exists()
    manifest_path = hub_root / ".codex-autorunner" / "manifest.yml"
    manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
    assert manifest["repos"][0]["id"] == "demo"
    assert manifest["repos"][0]["path"] == "workspace/demo"


def test_hub_create_repo_rejects_outside_repos_root(tmp_path: Path):
    hub_root = tmp_path / "hub"
    cfg = json.loads(json.dumps(DEFAULT_HUB_CONFIG))
    cfg["hub"]["repos_root"] = "workspace"
    write_test_config(hub_root / CONFIG_FILENAME, cfg)

    supervisor = HubSupervisor(
        load_hub_config(hub_root),
        backend_factory_builder=build_agent_backend_factory,
        app_server_supervisor_factory_builder=build_app_server_supervisor_factory,
    )
    with pytest.raises(ValueError):
        supervisor.create_repo("bad", repo_path=Path(".."))


def test_hub_create_repo_rejects_duplicate_id(tmp_path: Path):
    hub_root = tmp_path / "hub"
    cfg = json.loads(json.dumps(DEFAULT_HUB_CONFIG))
    cfg["hub"]["repos_root"] = "workspace"
    write_test_config(hub_root / CONFIG_FILENAME, cfg)

    supervisor = HubSupervisor(
        load_hub_config(hub_root),
        backend_factory_builder=build_agent_backend_factory,
        app_server_supervisor_factory_builder=build_app_server_supervisor_factory,
    )
    supervisor.create_repo("demo")
    with pytest.raises(ValueError, match="Repo id demo already exists"):
        supervisor.create_repo("demo", repo_path=Path("other"))


def test_hub_clone_repo_cli(tmp_path: Path):
    hub_root = tmp_path / "hub"
    cfg = json.loads(json.dumps(DEFAULT_HUB_CONFIG))
    cfg["hub"]["repos_root"] = "workspace"
    write_test_config(hub_root / CONFIG_FILENAME, cfg)

    source_repo = tmp_path / "source"
    _init_git_repo(source_repo)

    runner = CliRunner()
    result = runner.invoke(
        app,
        [
            "hub",
            "clone",
            "--git-url",
            str(source_repo),
            "--id",
            "cloned",
            "--path",
            str(hub_root),
        ],
    )
    assert result.exit_code == 0
    assert "Cloned repo cloned" in result.output

    repo_dir = hub_root / "workspace" / "cloned"
    assert (repo_dir / ".git").exists()
    assert (repo_dir / ".codex-autorunner" / "state.sqlite3").exists()


def test_hub_clone_repo_rejects_duplicate_id(tmp_path: Path):
    hub_root = tmp_path / "hub"
    cfg = json.loads(json.dumps(DEFAULT_HUB_CONFIG))
    cfg["hub"]["repos_root"] = "workspace"
    write_test_config(hub_root / CONFIG_FILENAME, cfg)

    source_repo = tmp_path / "source"
    _init_git_repo(source_repo)

    supervisor = HubSupervisor(
        load_hub_config(hub_root),
        backend_factory_builder=build_agent_backend_factory,
        app_server_supervisor_factory_builder=build_app_server_supervisor_factory,
    )
    supervisor.create_repo("demo")
    with pytest.raises(ValueError, match="Repo id demo already exists"):
        supervisor.clone_repo(
            git_url=str(source_repo),
            repo_id="demo",
            repo_path=Path("other"),
        )


def test_hub_clone_repo_cli_failure_message(tmp_path: Path):
    hub_root = tmp_path / "hub"
    cfg = json.loads(json.dumps(DEFAULT_HUB_CONFIG))
    write_test_config(hub_root / CONFIG_FILENAME, cfg)

    missing_repo = tmp_path / "missing"

    runner = CliRunner()
    result = runner.invoke(
        app,
        [
            "hub",
            "clone",
            "--git-url",
            str(missing_repo),
            "--path",
            str(hub_root),
        ],
    )
    assert result.exit_code == 1
    assert "git clone failed" in result.output
