"""
Tests for token tracking and savings computation.

Validates:
- Token counting (with tiktoken and fallback)
- Savings math: baseline vs actual, dollars saved
- Messages gained formula
"""

import pytest

from proxy.schemas import ChatMessage, TokenPricing
from proxy.token_tracker import (
    compute_savings,
    count_tokens,
    tokens_to_messages,
)


class TestCountTokens:
    def test_basic_message_counting(self):
        """Token count should be > 0 for any non-empty message."""
        messages = [ChatMessage(role="user", content="Hello, how are you?")]
        count = count_tokens(messages, "gpt-4o")
        assert count > 0

    def test_longer_messages_have_more_tokens(self):
        short = [ChatMessage(role="user", content="Hi")]
        long = [
            ChatMessage(
                role="user",
                content="Hello, this is a much longer message with many words",
            )
        ]
        assert count_tokens(long, "gpt-4o") > count_tokens(short, "gpt-4o")

    def test_multiple_messages_sum(self):
        one_msg = [ChatMessage(role="user", content="Hello world")]
        two_msgs = [
            ChatMessage(role="system", content="You are helpful."),
            ChatMessage(role="user", content="Hello world"),
        ]
        assert count_tokens(two_msgs, "gpt-4o") > count_tokens(one_msg, "gpt-4o")

    def test_empty_content_still_counts(self):
        """Even with empty content, overhead should produce tokens."""
        messages = [ChatMessage(role="user", content="")]
        count = count_tokens(messages, "gpt-4o")
        assert count > 0


class TestComputeSavings:
    @pytest.fixture
    def pricing(self):
        return TokenPricing(
            input_cost_per_1k=0.01,
            output_cost_per_1k=0.03,
            cached_input_cost_per_1k=0.005,
        )

    def test_no_savings_on_miss(self, pricing):
        savings = compute_savings(
            baseline_tokens=100,
            actual_tokens=100,
            completion_tokens=50,
            pricing=pricing,
            cache_hit=False,
        )
        assert savings.cached_tokens == 0
        assert savings.dollars_saved == 0.0
        assert savings.cache_hit is False

    def test_full_savings_on_cache_hit(self, pricing):
        savings = compute_savings(
            baseline_tokens=1000,
            actual_tokens=0,
            completion_tokens=500,
            pricing=pricing,
            cache_hit=True,
        )
        assert savings.cached_tokens > 0
        assert savings.dollars_saved > 0.0
        assert savings.cache_hit is True
        # Full cache hit: the entire request is free (no provider call),
        # so we save BOTH prompt and completion cost.
        expected = 1000 * 0.01 / 1000 + 500 * 0.03 / 1000  # input  # completion
        assert savings.dollars_saved == pytest.approx(expected, rel=0.01)

    def test_partial_savings(self, pricing):
        """When some tokens are cached but not all."""
        savings = compute_savings(
            baseline_tokens=1000,
            actual_tokens=500,
            completion_tokens=200,
            pricing=pricing,
            cache_hit=False,
        )
        assert savings.cached_tokens == 500
        # Saved 500 input tokens: 500 * $0.01/1k = $0.005
        expected = 500 * 0.01 / 1000
        assert savings.dollars_saved == pytest.approx(expected, rel=0.01)


class TestTokensToMessages:
    def test_basic_conversion(self):
        assert tokens_to_messages(750) == 1
        assert tokens_to_messages(1500) == 2
        assert tokens_to_messages(7500) == 10

    def test_rounds_down(self):
        assert tokens_to_messages(749) == 0
        assert tokens_to_messages(1499) == 1

    def test_custom_avg(self):
        assert tokens_to_messages(1000, avg_per_msg=500) == 2

    def test_zero_tokens(self):
        assert tokens_to_messages(0) == 0

    def test_zero_avg_returns_zero(self):
        assert tokens_to_messages(1000, avg_per_msg=0) == 0
