from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

import pytest

import custodian_cli
from custodian_cli import cmd_fix_consistency
from statehub_register import (
    RegisterInference,
    _invoke_llm,
    _normalise_inference,
    _parse_json_object,
    write_registration_files,
)


def test_parse_json_object_accepts_fenced_json():
    parsed = _parse_json_object(
        """Here is the answer:

```json
{"repo_slug": "demo-repo", "in_scope": ["tests"]}
```
"""
    )

    assert parsed == {"repo_slug": "demo-repo", "in_scope": ["tests"]}


def test_normalise_inference_slugifies_and_normalises_prefix():
    inference = _normalise_inference(
        {
            "project_description": "Provides demo automation.",
            "domain_slug": "Custodian",
            "repo_slug": "Demo Repo!",
            "workplan_prefix": "demo",
            "in_scope": ["one", "two"],
            "out_of_scope": ["three"],
        }
    )

    assert inference.domain_slug == "custodian"
    assert inference.repo_slug == "demo-repo"
    assert inference.workplan_prefix == "DEMO-WP"
    assert inference.in_scope == ["one", "two"]
    assert inference.out_of_scope == ["three"]


def test_invoke_llm_uses_mock_adapter(monkeypatch):
    payload = {"project_description": "Mocked repo.", "repo_slug": "mocked-repo"}
    monkeypatch.setenv("STATEHUB_REGISTER_MOCK_LLM_RESPONSE", json.dumps(payload))
    args = argparse.Namespace(
        llm_provider="mock",
        llm_model=None,
        llm_api_key=None,
        llm_timeout=5,
    )

    assert json.loads(_invoke_llm("infer this repo", args)) == payload


def test_write_registration_files_primes_codex_repo(tmp_path: Path):
    inference = RegisterInference(
        in_scope=["Run the demo service."],
        out_of_scope=["Operate unrelated services."],
        current_state="Status: active; implementation is small.",
    )

    written = write_registration_files(
        project_path=tmp_path,
        project_name="demo-service",
        project_description="Provides a demo service.",
        domain="custodian",
        topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
        topic_slug="custodian",
        repo_slug="demo-service",
        wp_prefix="DEMO-WP",
        intent_markdown="# INTENT\n\nDemo service intent.\n",
        inference=inference,
    )

    assert {path.name for path in written} == {
        "INTENT.md",
        "SCOPE.md",
        "AGENTS.md",
        ".custodian-brief.md",
        "DEMO-WP-0001-statehub-bootstrap.md",
    }
    assert (tmp_path / "INTENT.md").read_text() == "# INTENT\n\nDemo service intent.\n"
    assert "**Repo slug:** demo-service" in (tmp_path / "AGENTS.md").read_text()
    assert "Run the demo service." in (tmp_path / "SCOPE.md").read_text()
    workplan = (tmp_path / "workplans" / "DEMO-WP-0001-statehub-bootstrap.md").read_text()
    assert "id: DEMO-WP-0001" in workplan
    assert "id: DEMO-WP-0001-T01" in workplan
    assert "statehub fix-consistency" in workplan


def test_write_registration_files_is_idempotent_without_force(tmp_path: Path):
    inference = RegisterInference()
    kwargs = {
        "project_path": tmp_path,
        "project_name": "demo",
        "project_description": "Provides a demo.",
        "domain": "custodian",
        "topic_id": "topic",
        "topic_slug": "custodian",
        "repo_slug": "demo",
        "wp_prefix": "DEMO-WP",
        "intent_markdown": "# INTENT\n\nDemo.\n",
        "inference": inference,
    }

    assert write_registration_files(**kwargs)
    assert write_registration_files(**kwargs) == []


def _fix_args(**overrides):
    values = {
        "repo": None,
        "all": False,
        "path": None,
        "repo_path": None,
        "remote": False,
        "max_seconds": None,
        "no_writeback": False,
        "archive_closed": False,
        "archive_workplan": None,
        "archive_date": None,
        "api_base": "http://statehub.test",
        "as_json": False,
        "strict_warnings": False,
    }
    values.update(overrides)
    return argparse.Namespace(**values)


def _install_fake_checker(monkeypatch, tmp_path: Path) -> Path:
    checker = tmp_path / "scripts" / "consistency_check.py"
    checker.parent.mkdir()
    checker.write_text("#!/usr/bin/env python3\n", encoding="utf-8")
    monkeypatch.setattr(custodian_cli, "STATE_HUB_DIR", tmp_path)
    return checker


def test_fix_consistency_defaults_to_here_and_normalises_warning_exit(monkeypatch, tmp_path: Path):
    checker = _install_fake_checker(monkeypatch, tmp_path)
    repo = tmp_path / "repo"
    repo.mkdir()
    calls = []

    def fake_run(cmd):
        calls.append(cmd)
        return argparse.Namespace(returncode=2)

    monkeypatch.setattr(custodian_cli.subprocess, "run", fake_run)

    with pytest.raises(SystemExit) as exc:
        cmd_fix_consistency(_fix_args(path=str(repo)))

    assert exc.value.code == 0
    assert calls == [[
        sys.executable,
        str(checker),
        "--here",
        str(repo.resolve()),
        "--fix",
        "--api-base",
        "http://statehub.test",
    ]]


def test_fix_consistency_strict_warnings_preserves_exit_two(monkeypatch, tmp_path: Path):
    _install_fake_checker(monkeypatch, tmp_path)
    repo = tmp_path / "repo"
    repo.mkdir()
    monkeypatch.setattr(
        custodian_cli.subprocess,
        "run",
        lambda _cmd: argparse.Namespace(returncode=2),
    )

    with pytest.raises(SystemExit) as exc:
        cmd_fix_consistency(_fix_args(path=str(repo), strict_warnings=True))

    assert exc.value.code == 2


def test_fix_consistency_repo_remote_passes_pull_before_fix_options(monkeypatch, tmp_path: Path):
    checker = _install_fake_checker(monkeypatch, tmp_path)
    repo = tmp_path / "repo"
    repo.mkdir()
    calls = []

    def fake_run(cmd):
        calls.append(cmd)
        return argparse.Namespace(returncode=0)

    monkeypatch.setattr(custodian_cli.subprocess, "run", fake_run)

    with pytest.raises(SystemExit) as exc:
        cmd_fix_consistency(
            _fix_args(
                repo="demo-service",
                repo_path=str(repo),
                remote=True,
                no_writeback=True,
                as_json=True,
                max_seconds=12,
            )
        )

    assert exc.value.code == 0
    assert calls == [[
        sys.executable,
        str(checker),
        "--repo",
        "demo-service",
        "--repo-path",
        str(repo.resolve()),
        "--fix",
        "--remote",
        "--no-writeback",
        "--api-base",
        "http://statehub.test",
        "--json",
        "--max-seconds",
        "12",
    ]]


def test_fix_consistency_remote_requires_explicit_repo_or_all(monkeypatch, tmp_path: Path):
    _install_fake_checker(monkeypatch, tmp_path)
    calls = []
    monkeypatch.setattr(custodian_cli.subprocess, "run", lambda cmd: calls.append(cmd))

    with pytest.raises(SystemExit) as exc:
        cmd_fix_consistency(_fix_args(remote=True, path=str(tmp_path)))

    assert exc.value.code == 1
    assert calls == []
