"""
Credits Calculator Service

Lightweight, framework-agnostic credits calculation for TTS requests.
Portable from veena3srv for Modal deployment.

Credits model:
- Base credits from text length: 0.001 per character
- Audio duration credits: 0.01 per second
- Minimum charge: 0.0001 credits
"""

from decimal import Decimal
from typing import Optional, NamedTuple


class CreditsConfig(NamedTuple):
    """Configuration for credits calculation."""
    credit_per_character: Decimal = Decimal("0.001")
    credit_per_second: Decimal = Decimal("0.01")
    minimum_charge: Decimal = Decimal("0.0001")


# Default configuration
DEFAULT_CONFIG = CreditsConfig()


def calculate_credits(
    text_length: int,
    audio_duration_seconds: Optional[float] = None,
    config: Optional[CreditsConfig] = None,
) -> Decimal:
    """
    Calculate credits consumed for a TTS request.
    
    Uses text length as primary factor, with optional audio duration bonus.
    
    Args:
        text_length: Input text length in characters
        audio_duration_seconds: Generated audio duration (optional)
        config: Custom credits configuration (optional)
    
    Returns:
        Credits consumed as Decimal (4 decimal places)
    
    Example:
        >>> calculate_credits(1000)  # 1000 chars
        Decimal('1.0000')
        >>> calculate_credits(1000, 10.5)  # 1000 chars + 10.5 seconds
        Decimal('1.1050')
    """
    cfg = config or DEFAULT_CONFIG
    credits = Decimal("0.0000")
    
    # Base credits from text length
    credits += Decimal(str(text_length)) * cfg.credit_per_character
    
    # Additional credits from audio duration (if available)
    if audio_duration_seconds is not None and audio_duration_seconds > 0:
        credits += Decimal(str(audio_duration_seconds)) * cfg.credit_per_second
    
    # Ensure minimum charge
    if credits < cfg.minimum_charge:
        credits = cfg.minimum_charge
    
    # Round to 4 decimal places
    return credits.quantize(Decimal("0.0001"))


class CreditCheckResult(NamedTuple):
    """Result of credit check."""
    has_sufficient: bool
    required_credits: Decimal
    available_credits: Decimal
    error_message: Optional[str] = None


def check_credits(
    available_credits: Decimal,
    text_length: int,
    config: Optional[CreditsConfig] = None,
) -> CreditCheckResult:
    """
    Check if user has sufficient credits for a request.
    
    This is the "check-before-infer" part of the credits flow.
    
    Args:
        available_credits: User's current credit balance
        text_length: Input text length
        config: Custom credits configuration
    
    Returns:
        CreditCheckResult with check outcome
    """
    required = calculate_credits(text_length, config=config)
    has_sufficient = available_credits >= required
    
    error_message = None
    if not has_sufficient:
        error_message = f"Insufficient credits. Required: {required}, available: {available_credits}"
    
    return CreditCheckResult(
        has_sufficient=has_sufficient,
        required_credits=required,
        available_credits=available_credits,
        error_message=error_message,
    )


def consume_credits(
    text_length: int,
    audio_duration_seconds: Optional[float] = None,
    config: Optional[CreditsConfig] = None,
) -> Decimal:
    """
    Calculate credits to consume after successful generation.
    
    This is the "consume-after-success" part of the credits flow.
    Called only after audio generation completes successfully.
    
    Args:
        text_length: Input text length
        audio_duration_seconds: Actual audio duration generated
        config: Custom credits configuration
    
    Returns:
        Credits to deduct from user balance
    """
    return calculate_credits(text_length, audio_duration_seconds, config)


# Convenience function for estimating credits before generation
def estimate_credits(
    text_length: int,
    avg_chars_per_second: float = 15.0,
    config: Optional[CreditsConfig] = None,
) -> Decimal:
    """
    Estimate credits for a request (before generation).
    
    Uses average speech rate to estimate audio duration.
    
    Args:
        text_length: Input text length
        avg_chars_per_second: Average characters per second of speech
        config: Custom credits configuration
    
    Returns:
        Estimated credits
    """
    estimated_duration = text_length / avg_chars_per_second
    return calculate_credits(text_length, estimated_duration, config)

