from types import SimpleNamespace

from nipux_cli.config import ModelConfig
from nipux_cli.llm import OpenAIChatLLM, _enrich_openrouter_generation_usage


class _FakeCompletions:
    def __init__(self):
        self.kwargs = None
        self.calls = []

    def create(self, **kwargs):
        self.kwargs = kwargs
        self.calls.append(kwargs)
        usage = SimpleNamespace(prompt_tokens=11, completion_tokens=7, total_tokens=18, cost=0.00042)
        message = SimpleNamespace(content="ok", tool_calls=[])
        choice = SimpleNamespace(message=message)
        return SimpleNamespace(id="gen_test", model="provider/model", choices=[choice], usage=usage)


def test_chat_llm_requires_tool_choice_for_worker_actions(monkeypatch):
    fake_completions = _FakeCompletions()
    monkeypatch.setenv("TEST_API_KEY", "test")

    class FakeOpenAI:
        def __init__(self, **kwargs):
            pass

        chat = SimpleNamespace(completions=fake_completions)

    monkeypatch.setattr("nipux_cli.llm.OpenAI", FakeOpenAI)

    llm = OpenAIChatLLM(ModelConfig(model="test/model", base_url="https://example.test/v1", api_key_env="TEST_API_KEY"))
    response = llm.next_action(messages=[{"role": "user", "content": "hi"}], tools=[{"type": "function", "function": {"name": "noop"}}])

    assert response.content == "ok"
    assert response.usage["prompt_tokens"] == 11
    assert response.usage["completion_tokens"] == 7
    assert response.usage["cost"] == 0.00042
    assert response.model == "provider/model"
    assert response.response_id == "gen_test"
    assert fake_completions.kwargs["tools"]
    assert fake_completions.kwargs["tool_choice"] == "required"


def test_chat_llm_sets_low_reasoning_for_openrouter(monkeypatch):
    fake_completions = _FakeCompletions()
    monkeypatch.setenv("TEST_API_KEY", "test")

    class FakeOpenAI:
        def __init__(self, **kwargs):
            pass

        chat = SimpleNamespace(completions=fake_completions)

    monkeypatch.setattr("nipux_cli.llm.OpenAI", FakeOpenAI)

    llm = OpenAIChatLLM(ModelConfig(model="test/model", base_url="https://openrouter.ai/api/v1", api_key_env="TEST_API_KEY"))
    llm.next_action(messages=[{"role": "user", "content": "hi"}], tools=[{"type": "function", "function": {"name": "noop"}}])

    assert fake_completions.kwargs["extra_body"] == {"reasoning": {"effort": "low", "exclude": True}}


def test_chat_llm_retries_without_tool_choice_when_provider_rejects_it(monkeypatch):
    monkeypatch.setenv("TEST_API_KEY", "test")

    class RejectingCompletions(_FakeCompletions):
        def create(self, **kwargs):
            self.calls.append(kwargs)
            if kwargs.get("tool_choice") == "required":
                raise RuntimeError("unsupported parameter: tool_choice")
            return super().create(**kwargs)

    fake_completions = RejectingCompletions()

    class FakeOpenAI:
        def __init__(self, **kwargs):
            pass

        chat = SimpleNamespace(completions=fake_completions)

    monkeypatch.setattr("nipux_cli.llm.OpenAI", FakeOpenAI)

    llm = OpenAIChatLLM(ModelConfig(model="test/model", base_url="https://example.test/v1", api_key_env="TEST_API_KEY"))
    response = llm.next_action(messages=[{"role": "user", "content": "hi"}], tools=[{"type": "function", "function": {"name": "noop"}}])

    assert response.content == "ok"
    assert fake_completions.calls[0]["tool_choice"] == "required"
    assert "tool_choice" not in fake_completions.calls[-1]


def test_chat_llm_complete_response_returns_usage(monkeypatch):
    fake_completions = _FakeCompletions()
    monkeypatch.setenv("TEST_API_KEY", "test")

    class FakeOpenAI:
        def __init__(self, **kwargs):
            pass

        chat = SimpleNamespace(completions=fake_completions)

    monkeypatch.setattr("nipux_cli.llm.OpenAI", FakeOpenAI)

    llm = OpenAIChatLLM(ModelConfig(model="test/model", base_url="https://example.test/v1", api_key_env="TEST_API_KEY"))
    response = llm.complete_response(messages=[{"role": "user", "content": "hi"}])

    assert response.content == "ok"
    assert response.usage["prompt_tokens"] == 11
    assert response.usage["completion_tokens"] == 7
    assert response.usage["cost"] == 0.00042
    assert response.model == "provider/model"
    assert response.response_id == "gen_test"
    assert fake_completions.kwargs["model"] == "test/model"


def test_chat_llm_disables_provider_sdk_retries(monkeypatch):
    captured = {}
    monkeypatch.setenv("TEST_API_KEY", "test")

    class FakeOpenAI:
        def __init__(self, **kwargs):
            captured.update(kwargs)

        chat = SimpleNamespace(completions=_FakeCompletions())

    monkeypatch.setattr("nipux_cli.llm.OpenAI", FakeOpenAI)

    OpenAIChatLLM(ModelConfig(model="test/model", base_url="https://example.test/v1", api_key_env="TEST_API_KEY", request_timeout_seconds=37))

    assert captured["timeout"] == 37
    assert captured["max_retries"] == 0


def test_openrouter_generation_usage_enriches_cost_and_tokens(monkeypatch):
    class FakeHTTPResponse:
        def __enter__(self):
            return self

        def __exit__(self, *_args):
            return False

        def read(self):
            return (
                b'{"data":{"total_cost":"0.0042","native_tokens_prompt":123,'
                b'"native_tokens_completion":45,"native_tokens_total":168}}'
            )

    captured = {}

    def fake_urlopen(request, timeout):
        captured["url"] = request.full_url
        captured["timeout"] = timeout
        return FakeHTTPResponse()

    monkeypatch.setattr("nipux_cli.llm.urllib.request.urlopen", fake_urlopen)

    usage = _enrich_openrouter_generation_usage(
        {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15, "estimated": False},
        response_id="gen_123",
        base_url="https://openrouter.ai/api/v1",
        api_key="sk-test",
    )

    assert captured["url"] == "https://openrouter.ai/api/v1/generation?id=gen_123"
    assert captured["timeout"] == 5
    assert usage["cost"] == 0.0042
    assert usage["prompt_tokens"] == 123
    assert usage["completion_tokens"] == 45
    assert usage["total_tokens"] == 168
