from __future__ import annotations

import json
import subprocess
from types import SimpleNamespace

import pytest

from recoil.pipeline._lib.opus_oauth import OpusOAuthError, call_opus_oauth


def test_call_opus_oauth_invokes_claude_with_prompt_env_and_schema(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setenv("ANTHROPIC_API_KEY", "paid-api-key")
    seen: dict[str, object] = {}
    schema = {
        "type": "object",
        "properties": {"answer": {"type": "string"}},
        "required": ["answer"],
    }

    def fake_run(*args, **kwargs):
        seen["args"] = args
        seen["kwargs"] = kwargs
        return SimpleNamespace(stdout="  structured text\n", stderr="", returncode=0)

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

    text = call_opus_oauth(
        "claude-opus-4-8",
        "system prompt",
        "user prompt",
        timeout=321,
        effort="high",
        json_schema=schema,
    )

    assert text == "structured text"
    assert len(seen["args"]) == 1
    argv = seen["args"][0]
    assert argv[:4] == [
        "claude",
        "--print",
        "--permission-mode",
        "bypassPermissions",
    ]
    assert argv[argv.index("--model") + 1] == "claude-opus-4-8"
    assert argv[argv.index("--append-system-prompt") + 1] == "system prompt"
    assert argv[argv.index("--effort") + 1] == "high"

    kwargs = seen["kwargs"]
    assert kwargs["input"].startswith("user prompt")
    assert json.dumps(schema) in kwargs["input"]
    assert "required top-level key" in kwargs["input"]
    assert kwargs["capture_output"] is True
    assert kwargs["text"] is True
    assert kwargs["timeout"] == 321
    assert kwargs["check"] is False
    assert "ANTHROPIC_API_KEY" not in kwargs["env"]


def test_call_opus_oauth_omits_optional_flags_when_not_given(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    seen: dict[str, object] = {}

    def fake_run(*args, **kwargs):
        seen["argv"] = args[0]
        return SimpleNamespace(stdout="ok", stderr="", returncode=0)

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

    assert call_opus_oauth("claude-opus-4-8", "sys", "user") == "ok"

    argv = seen["argv"]
    assert "--effort" not in argv
    assert "--json-schema" not in argv


def test_no_json_schema_cli_flag(monkeypatch: pytest.MonkeyPatch) -> None:
    seen: dict[str, object] = {}
    json_schema = {
        "type": "object",
        "properties": {"answer": {"type": "string"}},
        "required": ["answer"],
    }

    def fake_run(*args, **kwargs):
        seen["argv"] = args[0]
        seen["input"] = kwargs["input"]
        return SimpleNamespace(stdout="ok", stderr="", returncode=0)

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

    assert call_opus_oauth("claude-opus-4-8", "sys", "user", json_schema=json_schema) == "ok"

    argv = seen["argv"]
    assert "--json-schema" not in argv


def test_schema_inlined_into_prompt(monkeypatch: pytest.MonkeyPatch) -> None:
    seen: dict[str, object] = {}
    json_schema = {
        "type": "object",
        "properties": {"answer": {"type": "string"}},
        "required": ["answer"],
    }

    def fake_run(*args, **kwargs):
        seen["input"] = kwargs["input"]
        return SimpleNamespace(stdout="ok", stderr="", returncode=0)

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

    assert call_opus_oauth("claude-opus-4-8", "sys", "user", json_schema=json_schema) == "ok"

    assert json.dumps(json_schema) in seen["input"]
    assert "required top-level key" in seen["input"]


def test_no_schema_prompt_unchanged(monkeypatch: pytest.MonkeyPatch) -> None:
    seen: dict[str, object] = {}
    user_prompt = "user prompt"

    def fake_run(*args, **kwargs):
        seen["argv"] = args[0]
        seen["input"] = kwargs["input"]
        return SimpleNamespace(stdout="ok", stderr="", returncode=0)

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

    assert call_opus_oauth("claude-opus-4-8", "sys", user_prompt, json_schema=None) == "ok"

    argv = seen["argv"]
    assert seen["input"] == user_prompt
    assert "--json-schema" not in argv


def test_call_opus_oauth_nonzero_exit_raises_typed_error_with_context(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    def fake_run(*args, **kwargs):
        return SimpleNamespace(
            stdout="",
            stderr="auth failed: subscription login required",
            returncode=7,
        )

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

    with pytest.raises(OpusOAuthError) as excinfo:
        call_opus_oauth("claude-opus-4-8", "sys", "user")

    err = excinfo.value
    assert err.returncode == 7
    assert err.model_id == "claude-opus-4-8"
    assert "auth failed" in err.stderr_excerpt
    assert "returncode=7" in str(err)
    assert "claude-opus-4-8" in str(err)


def test_call_opus_oauth_missing_binary_maps_to_typed_error(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    def fake_run(*args, **kwargs):
        raise FileNotFoundError("claude")

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

    with pytest.raises(OpusOAuthError) as excinfo:
        call_opus_oauth("claude-opus-4-8", "sys", "user")

    err = excinfo.value
    assert err.returncode == -1
    assert err.model_id == "claude-opus-4-8"
    assert "claude" in err.stderr_excerpt


def test_call_opus_oauth_timeout_maps_to_typed_error_with_timeout(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    def fake_run(*args, **kwargs):
        raise subprocess.TimeoutExpired(
            cmd=args[0],
            timeout=kwargs["timeout"],
            stderr="partial timeout stderr",
        )

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

    with pytest.raises(OpusOAuthError) as excinfo:
        call_opus_oauth("claude-opus-4-8", "sys", "user", timeout=12)

    err = excinfo.value
    assert err.returncode == 124
    assert err.timeout == 12
    assert err.model_id == "claude-opus-4-8"
    assert "partial timeout stderr" in err.stderr_excerpt


@pytest.mark.parametrize("stdout", ["", " \n\t "])
def test_call_opus_oauth_empty_output_raises_typed_error(
    monkeypatch: pytest.MonkeyPatch,
    stdout: str,
) -> None:
    def fake_run(*args, **kwargs):
        return SimpleNamespace(stdout=stdout, stderr="no content", returncode=0)

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

    with pytest.raises(OpusOAuthError) as excinfo:
        call_opus_oauth("claude-opus-4-8", "sys", "user")

    err = excinfo.value
    assert err.returncode == 0
    assert err.model_id == "claude-opus-4-8"
    assert "no content" in err.stderr_excerpt
