"""
Unit tests for credits calculator service.

Tests credit calculation, checking, and consumption.
"""

import pytest
from decimal import Decimal

from veena3modal.services.credits import (
    calculate_credits,
    check_credits,
    consume_credits,
    estimate_credits,
    CreditsConfig,
    CreditCheckResult,
    DEFAULT_CONFIG,
)


class TestCalculateCredits:
    """Test calculate_credits function."""
    
    def test_text_only(self):
        """Calculate credits from text length only."""
        # 1000 chars * 0.001 = 1.0
        result = calculate_credits(1000)
        assert result == Decimal("1.0000")
    
    def test_text_with_audio_duration(self):
        """Calculate credits with audio duration bonus."""
        # 1000 chars * 0.001 + 10 seconds * 0.01 = 1.1
        result = calculate_credits(1000, 10.0)
        assert result == Decimal("1.1000")
    
    def test_short_text(self):
        """Calculate credits for short text."""
        # 10 chars * 0.001 = 0.01
        result = calculate_credits(10)
        assert result == Decimal("0.0100")
    
    def test_very_short_text_minimum_charge(self):
        """Minimum charge should be applied for tiny requests."""
        # 0 chars would be 0, but minimum is 0.0001
        result = calculate_credits(0)
        assert result == Decimal("0.0001")
    
    def test_long_text(self):
        """Calculate credits for long text (50K chars)."""
        # 50000 chars * 0.001 = 50.0
        result = calculate_credits(50000)
        assert result == Decimal("50.0000")
    
    def test_precision(self):
        """Credits should be rounded to 4 decimal places."""
        # 123 chars * 0.001 = 0.123
        result = calculate_credits(123)
        assert result == Decimal("0.1230")
        assert str(result) == "0.1230"
    
    def test_custom_config(self):
        """Custom config should override defaults."""
        config = CreditsConfig(
            credit_per_character=Decimal("0.01"),
            credit_per_second=Decimal("0.1"),
            minimum_charge=Decimal("0.001"),
        )
        # 100 chars * 0.01 + 5 seconds * 0.1 = 1.5
        result = calculate_credits(100, 5.0, config)
        assert result == Decimal("1.5000")
    
    def test_zero_audio_duration_ignored(self):
        """Zero audio duration should not add credits."""
        result = calculate_credits(100, 0.0)
        assert result == calculate_credits(100)
    
    def test_negative_audio_duration_ignored(self):
        """Negative audio duration should not add credits."""
        result = calculate_credits(100, -5.0)
        assert result == calculate_credits(100)


class TestCheckCredits:
    """Test check_credits function."""
    
    def test_sufficient_credits(self):
        """User has enough credits."""
        result = check_credits(
            available_credits=Decimal("10.0000"),
            text_length=1000,  # requires 1.0 credits
        )
        assert result.has_sufficient is True
        assert result.required_credits == Decimal("1.0000")
        assert result.available_credits == Decimal("10.0000")
        assert result.error_message is None
    
    def test_insufficient_credits(self):
        """User doesn't have enough credits."""
        result = check_credits(
            available_credits=Decimal("0.5000"),
            text_length=1000,  # requires 1.0 credits
        )
        assert result.has_sufficient is False
        assert result.required_credits == Decimal("1.0000")
        assert result.available_credits == Decimal("0.5000")
        assert "Insufficient credits" in result.error_message
    
    def test_exact_credits(self):
        """User has exactly enough credits."""
        result = check_credits(
            available_credits=Decimal("1.0000"),
            text_length=1000,  # requires 1.0 credits
        )
        assert result.has_sufficient is True
        assert result.error_message is None
    
    def test_zero_credits(self):
        """User has zero credits."""
        result = check_credits(
            available_credits=Decimal("0.0000"),
            text_length=100,
        )
        assert result.has_sufficient is False


class TestConsumeCredits:
    """Test consume_credits function."""
    
    def test_consume_after_success(self):
        """Calculate credits to consume after successful generation."""
        credits = consume_credits(
            text_length=1000,
            audio_duration_seconds=10.5,
        )
        # 1000 * 0.001 + 10.5 * 0.01 = 1.105
        assert credits == Decimal("1.1050")
    
    def test_consume_text_only(self):
        """Consume credits when audio duration unknown."""
        credits = consume_credits(text_length=500)
        # 500 * 0.001 = 0.5
        assert credits == Decimal("0.5000")


class TestEstimateCredits:
    """Test estimate_credits function."""
    
    def test_estimate_default_rate(self):
        """Estimate with default speech rate (15 chars/sec)."""
        credits = estimate_credits(150)  # 150 chars
        # Text: 150 * 0.001 = 0.15
        # Audio estimate: 150/15 = 10 seconds, 10 * 0.01 = 0.1
        # Total: 0.25
        assert credits == Decimal("0.2500")
    
    def test_estimate_custom_rate(self):
        """Estimate with custom speech rate."""
        credits = estimate_credits(100, avg_chars_per_second=10.0)
        # Text: 100 * 0.001 = 0.1
        # Audio estimate: 100/10 = 10 seconds, 10 * 0.01 = 0.1
        # Total: 0.2
        assert credits == Decimal("0.2000")
    
    def test_estimate_for_long_text(self):
        """Estimate for long text."""
        credits = estimate_credits(15000)  # 1000 seconds of speech
        # Text: 15000 * 0.001 = 15.0
        # Audio: 15000/15 = 1000 seconds * 0.01 = 10.0
        # Total: 25.0
        assert credits == Decimal("25.0000")


class TestDefaultConfig:
    """Test default configuration values."""
    
    def test_default_credit_per_character(self):
        """Default character rate is 0.001."""
        assert DEFAULT_CONFIG.credit_per_character == Decimal("0.001")
    
    def test_default_credit_per_second(self):
        """Default audio rate is 0.01."""
        assert DEFAULT_CONFIG.credit_per_second == Decimal("0.01")
    
    def test_default_minimum_charge(self):
        """Default minimum charge is 0.0001."""
        assert DEFAULT_CONFIG.minimum_charge == Decimal("0.0001")


class TestCreditCheckResult:
    """Test CreditCheckResult named tuple."""
    
    def test_result_is_namedtuple(self):
        """Result should be a named tuple."""
        result = CreditCheckResult(
            has_sufficient=True,
            required_credits=Decimal("1.0"),
            available_credits=Decimal("10.0"),
        )
        assert hasattr(result, "has_sufficient")
        assert hasattr(result, "required_credits")
        assert hasattr(result, "available_credits")
        assert hasattr(result, "error_message")
    
    def test_result_default_error_message(self):
        """Default error message is None."""
        result = CreditCheckResult(
            has_sufficient=True,
            required_credits=Decimal("1.0"),
            available_credits=Decimal("10.0"),
        )
        assert result.error_message is None

