"""Tests for LLM client abstraction and router."""

import os
from unittest.mock import patch

import pytest

from bilgeai.llm.base import LLMResponse
from bilgeai.llm.cost_tracker import CostTracker, estimate_cost, estimate_run_cost
from bilgeai.llm.remote import _extract_json_from_response


class TestLLMResponse:
    def test_basic_response(self):
        resp = LLMResponse(content="hello", model="test", source="local")
        assert resp.content == "hello"
        assert resp.tokens_used is None

    def test_response_with_tokens(self):
        resp = LLMResponse(content="hello", model="test", source="remote", tokens_used=100)
        assert resp.tokens_used == 100


class TestLLMRouter:
    def test_cloud_provider_requires_api_key(self):
        """Cloud provider without API key should raise LLMError."""
        from bilgeai.core.exceptions import LLMError

        with patch.dict(os.environ, {}, clear=True), pytest.raises(LLMError, match="API key"):
            from bilgeai.llm.router import get_llm_client

            get_llm_client(
                provider="anthropic",
                model="claude-sonnet-4-20250514",
                api_key_env="ANTHROPIC_API_KEY",
            )

    def test_local_provider_no_key_needed(self):
        """Local provider should create client without API key."""
        from bilgeai.llm.router import get_llm_client

        client = get_llm_client(
            provider="local",
            model="llama3.1",
            api_key_env="",
            base_url="http://localhost:11434/v1",
        )
        assert client is not None
        assert client.source_name == "local_llm"
        assert client.is_local is True

    def test_cloud_provider_with_api_key(self):
        """Cloud provider with API key should create client."""
        from bilgeai.llm.router import get_llm_client

        with patch.dict(os.environ, {"TEST_API_KEY": "sk-test123"}):
            client = get_llm_client(
                provider="openai",
                model="gpt-4o",
                api_key_env="TEST_API_KEY",
            )
            assert client is not None
            assert client.source_name == "remote_api"


class TestExtractJsonFromResponse:
    def test_direct_json(self):
        result = _extract_json_from_response('{"batch_size": 8}')
        assert result == {"batch_size": 8}

    def test_markdown_fenced_json(self):
        result = _extract_json_from_response('```json\n{"batch_size": 8}\n```')
        assert result == {"batch_size": 8}

    def test_json_with_surrounding_text(self):
        result = _extract_json_from_response(
            'Here is the config:\n{"batch_size": 8}\nHope this helps!'
        )
        assert result == {"batch_size": 8}

    def test_invalid_json_raises(self):
        from bilgeai.core.exceptions import LLMError

        with pytest.raises(LLMError):
            _extract_json_from_response("not json at all")


class TestCostTracker:
    def test_estimate_cost_known_model(self):
        cost = estimate_cost("claude-sonnet-4-20250514", 1000)
        assert cost > 0

    def test_estimate_cost_unknown_model(self):
        cost = estimate_cost("unknown-model-xyz", 1000)
        assert cost > 0  # Falls back to conservative estimate

    def test_estimate_run_cost_local(self):
        cost = estimate_run_cost("local", "llama3.1")
        assert cost == 0.0

    def test_estimate_run_cost_cloud(self):
        cost = estimate_run_cost("anthropic", "claude-sonnet-4-20250514")
        assert cost > 0

    def test_tracker_record_and_summary(self):
        tracker = CostTracker()
        tracker.record("claude-sonnet-4-20250514", 500, purpose="test")
        tracker.record("claude-sonnet-4-20250514", 300, purpose="test2")

        assert tracker.total_tokens == 800
        assert tracker.total_cost_usd > 0
        summary = tracker.summary()
        assert summary["total_calls"] == 2
