"""
Credit Service for managing credit transactions and deductions
"""
from django.db import transaction
from django.utils import timezone
from igny8_core.business.billing.models import CreditTransaction, CreditUsageLog
from igny8_core.business.billing.constants import CREDIT_COSTS
from igny8_core.business.billing.exceptions import InsufficientCreditsError, CreditCalculationError
from igny8_core.auth.models import Account


class CreditService:
    """Service for managing credits - Token-based only"""
    
    @staticmethod
    def calculate_credits_from_tokens(operation_type, tokens_input, tokens_output):
        """
        Calculate credits from actual token usage using configured ratio.
        This is the ONLY way credits are calculated in the system.
        
        Args:
            operation_type: Type of operation
            tokens_input: Input tokens used
            tokens_output: Output tokens used
        
        Returns:
            int: Credits to deduct
            
        Raises:
            CreditCalculationError: If configuration error
        """
        import logging
        import math
        from igny8_core.business.billing.models import CreditCostConfig, BillingConfiguration
        
        logger = logging.getLogger(__name__)
        
        # Get operation config (use global default if not found)
        config = CreditCostConfig.objects.filter(
            operation_type=operation_type,
            is_active=True
        ).first()
        
        if not config:
            # Use global billing config as fallback
            billing_config = BillingConfiguration.get_config()
            tokens_per_credit = billing_config.default_tokens_per_credit
            min_credits = 1
            logger.info(f"No config for {operation_type}, using default: {tokens_per_credit} tokens/credit")
        else:
            tokens_per_credit = config.tokens_per_credit
            min_credits = config.min_credits
        
        # Calculate total tokens
        total_tokens = (tokens_input or 0) + (tokens_output or 0)
        
        # Calculate credits (fractional)
        if tokens_per_credit <= 0:
            raise CreditCalculationError(f"Invalid tokens_per_credit: {tokens_per_credit}")
        
        credits_float = total_tokens / tokens_per_credit
        
        # Get rounding mode from global config
        billing_config = BillingConfiguration.get_config()
        rounding_mode = billing_config.credit_rounding_mode
        
        if rounding_mode == 'up':
            credits = math.ceil(credits_float)
        elif rounding_mode == 'down':
            credits = math.floor(credits_float)
        else:  # nearest
            credits = round(credits_float)
        
        # Apply minimum
        credits = max(credits, min_credits)
        
        logger.info(
            f"Calculated credits for {operation_type}: "
            f"{total_tokens} tokens ({tokens_input} in, {tokens_output} out) "
            f"÷ {tokens_per_credit} = {credits} credits"
        )
        
        return credits
    
    @staticmethod
    def check_credits_for_tokens(account, operation_type, estimated_tokens_input, estimated_tokens_output):
        """
        Check if account has sufficient credits based on estimated token usage.
        
        Args:
            account: Account instance
            operation_type: Type of operation
            estimated_tokens_input: Estimated input tokens
            estimated_tokens_output: Estimated output tokens
        
        Raises:
            InsufficientCreditsError: If account doesn't have enough credits
        """
        required = CreditService.calculate_credits_from_tokens(
            operation_type, estimated_tokens_input, estimated_tokens_output
        )
        if account.credits < required:
            raise InsufficientCreditsError(
                f"Insufficient credits. Required: {required}, Available: {account.credits}"
            )
        return True
    
    @staticmethod
    @transaction.atomic
    def deduct_credits(account, amount, operation_type, description, metadata=None, cost_usd=None, model_used=None, tokens_input=None, tokens_output=None, related_object_type=None, related_object_id=None):
        """
        Deduct credits and log transaction.
        
        Args:
            account: Account instance
            amount: Number of credits to deduct
            operation_type: Type of operation (from CreditUsageLog.OPERATION_TYPE_CHOICES)
            description: Description of the transaction
            metadata: Optional metadata dict
            cost_usd: Optional cost in USD
            model_used: Optional AI model used
            tokens_input: Optional input tokens
            tokens_output: Optional output tokens
            related_object_type: Optional related object type
            related_object_id: Optional related object ID
        
        Returns:
            int: New credit balance
        """
        # Check sufficient credits (legacy: amount is already calculated)
        CreditService.check_credits_legacy(account, amount)
        
        # Deduct from account.credits
        account.credits -= amount
        account.save(update_fields=['credits'])
        
        # Create CreditTransaction
        CreditTransaction.objects.create(
            account=account,
            transaction_type='deduction',
            amount=-amount,  # Negative for deduction
            balance_after=account.credits,
            description=description,
            metadata=metadata or {}
        )
        
        # Create CreditUsageLog
        CreditUsageLog.objects.create(
            account=account,
            operation_type=operation_type,
            credits_used=amount,
            cost_usd=cost_usd,
            model_used=model_used or '',
            tokens_input=tokens_input,
            tokens_output=tokens_output,
            related_object_type=related_object_type or '',
            related_object_id=related_object_id,
            metadata=metadata or {}
        )
        
        return account.credits
    
    @staticmethod
    @transaction.atomic
    def deduct_credits_for_operation(
        account, 
        operation_type, 
        tokens_input, 
        tokens_output,
        description=None, 
        metadata=None, 
        cost_usd=None, 
        model_used=None, 
        related_object_type=None, 
        related_object_id=None
    ):
        """
        Deduct credits for an operation based on actual token usage.
        This is the ONLY way to deduct credits in the token-based system.
        
        Args:
            account: Account instance
            operation_type: Type of operation
            tokens_input: REQUIRED - Actual input tokens used
            tokens_output: REQUIRED - Actual output tokens used
            description: Optional description (auto-generated if not provided)
            metadata: Optional metadata dict
            cost_usd: Optional cost in USD
            model_used: Optional AI model used
            related_object_type: Optional related object type
            related_object_id: Optional related object ID
        
        Returns:
            int: New credit balance
            
        Raises:
            ValueError: If tokens_input or tokens_output not provided
        """
        # Validate token inputs
        if tokens_input is None or tokens_output is None:
            raise ValueError(
                f"tokens_input and tokens_output are REQUIRED for credit deduction. "
                f"Got: tokens_input={tokens_input}, tokens_output={tokens_output}"
            )
        
        # Calculate credits from actual token usage
        credits_required = CreditService.calculate_credits_from_tokens(
            operation_type, tokens_input, tokens_output
        )
        
        # Check sufficient credits
        if account.credits < credits_required:
            raise InsufficientCreditsError(
                f"Insufficient credits. Required: {credits_required}, Available: {account.credits}"
            )
        
        # Auto-generate description if not provided
        if not description:
            total_tokens = tokens_input + tokens_output
            description = (
                f"{operation_type}: {total_tokens} tokens "
                f"({tokens_input} in, {tokens_output} out) = {credits_required} credits"
            )
        
        return CreditService.deduct_credits(
            account=account,
            amount=credits_required,
            operation_type=operation_type,
            description=description,
            metadata=metadata,
            cost_usd=cost_usd,
            model_used=model_used,
            tokens_input=tokens_input,
            tokens_output=tokens_output,
            related_object_type=related_object_type,
            related_object_id=related_object_id
        )
    
    @staticmethod
    @transaction.atomic
    def add_credits(account, amount, transaction_type, description, metadata=None):
        """
        Add credits (purchase, subscription, etc.).
        
        Args:
            account: Account instance
            amount: Number of credits to add
            transaction_type: Type of transaction (from CreditTransaction.TRANSACTION_TYPE_CHOICES)
            description: Description of the transaction
            metadata: Optional metadata dict
        
        Returns:
            int: New credit balance
        """
        # Add to account.credits
        account.credits += amount
        account.save(update_fields=['credits'])
        
        # Create CreditTransaction
        CreditTransaction.objects.create(
            account=account,
            transaction_type=transaction_type,
            amount=amount,  # Positive for addition
            balance_after=account.credits,
            description=description,
            metadata=metadata or {}
        )
        
        return account.credits

