"""
Limit Service for Plan Limit Enforcement
Manages hard limits (sites, users, keywords, clusters) and monthly limits (ideas, words, images, prompts)
"""
from django.db import transaction
from django.utils import timezone
from datetime import timedelta
import logging

from igny8_core.auth.models import Account

logger = logging.getLogger(__name__)


class LimitExceededError(Exception):
    """Base exception for limit exceeded errors"""
    pass


class HardLimitExceededError(LimitExceededError):
    """Raised when a hard limit (sites, users, keywords, clusters) is exceeded"""
    pass


class MonthlyLimitExceededError(LimitExceededError):
    """Raised when a monthly limit (ideas, words, images, prompts) is exceeded"""
    pass


class LimitService:
    """Service for managing and enforcing plan limits"""
    
    # Map limit types to model/field names
    HARD_LIMIT_MAPPINGS = {
        'sites': {
            'model': 'igny8_core_auth.Site',
            'plan_field': 'max_sites',
            'display_name': 'Sites',
            'filter_field': 'account',
        },
        'users': {
            'model': 'igny8_core_auth.SiteUserAccess',
            'plan_field': 'max_users',
            'display_name': 'Team Users',
            'filter_field': 'site__account',
        },
        'keywords': {
            'model': 'planner.Keywords',
            'plan_field': 'max_keywords',
            'display_name': 'Keywords',
            'filter_field': 'account',
        },
        'clusters': {
            'model': 'planner.Clusters',
            'plan_field': 'max_clusters',
            'display_name': 'Clusters',
            'filter_field': 'account',
        },
    }
    
    MONTHLY_LIMIT_MAPPINGS = {
        'content_ideas': {
            'plan_field': 'max_content_ideas',
            'usage_field': 'usage_content_ideas',
            'display_name': 'Content Ideas',
        },
        'content_words': {
            'plan_field': 'max_content_words',
            'usage_field': 'usage_content_words',
            'display_name': 'Content Words',
        },
        'images_basic': {
            'plan_field': 'max_images_basic',
            'usage_field': 'usage_images_basic',
            'display_name': 'Basic Images',
        },
        'images_premium': {
            'plan_field': 'max_images_premium',
            'usage_field': 'usage_images_premium',
            'display_name': 'Premium Images',
        },
        'image_prompts': {
            'plan_field': 'max_image_prompts',
            'usage_field': 'usage_image_prompts',
            'display_name': 'Image Prompts',
        },
    }
    
    @staticmethod
    def check_hard_limit(account: Account, limit_type: str, additional_count: int = 1) -> bool:
        """
        Check if adding items would exceed hard limit.
        
        Args:
            account: Account instance
            limit_type: Type of limit
            additional_count: Number of items to add
        
        Returns:
            bool: True if within limit
            
        Raises:
            HardLimitExceededError: If limit would be exceeded
        """
        from django.apps import apps
        
        if limit_type not in LimitService.HARD_LIMIT_MAPPINGS:
            raise ValueError(f"Invalid hard limit type: {limit_type}")
        
        config = LimitService.HARD_LIMIT_MAPPINGS[limit_type]
        plan = account.plan
        
        if not plan:
            raise ValueError("Account has no plan")
        
        plan_limit = getattr(plan, config['plan_field'])
        model_path = config['model']
        app_label, model_name = model_path.split('.')
        Model = apps.get_model(app_label, model_name)
        
        filter_field = config.get('filter_field', 'account')
        filter_kwargs = {filter_field: account}
        current_count = Model.objects.filter(**filter_kwargs).count()
        new_count = current_count + additional_count
        
        logger.info(f"Hard limit check: {limit_type} - Current: {current_count}, Requested: {additional_count}, Limit: {plan_limit}")
        
        if new_count > plan_limit:
            raise HardLimitExceededError(
                f"{config['display_name']} limit exceeded. "
                f"Current: {current_count}, Limit: {plan_limit}. "
                f"Upgrade your plan to increase this limit."
            )
        
        return True
    
    @staticmethod
    def check_monthly_limit(account: Account, limit_type: str, amount: int = 1) -> bool:
        """
        Check if operation would exceed monthly limit.
        
        Args:
            account: Account instance
            limit_type: Type of limit
            amount: Amount to use
        
        Returns:
            bool: True if within limit
            
        Raises:
            MonthlyLimitExceededError: If limit would be exceeded
        """
        if limit_type not in LimitService.MONTHLY_LIMIT_MAPPINGS:
            raise ValueError(f"Invalid monthly limit type: {limit_type}")
        
        config = LimitService.MONTHLY_LIMIT_MAPPINGS[limit_type]
        plan = account.plan
        
        if not plan:
            raise ValueError("Account has no plan")
        
        plan_limit = getattr(plan, config['plan_field'])
        current_usage = getattr(account, config['usage_field'], 0)
        new_usage = current_usage + amount
        
        logger.info(f"Monthly limit check: {limit_type} - Current: {current_usage}, Requested: {amount}, Limit: {plan_limit}")
        
        if new_usage > plan_limit:
            period_end = account.usage_period_end or timezone.now().date()
            raise MonthlyLimitExceededError(
                f"{config['display_name']} limit exceeded. "
                f"Used: {current_usage}, Requested: {amount}, Limit: {plan_limit}. "
                f"Resets on {period_end.strftime('%B %d, %Y')}. "
                f"Upgrade your plan or wait for reset."
            )
        
        return True
    
    @staticmethod
    @transaction.atomic
    def increment_usage(account: Account, limit_type: str, amount: int = 1, metadata: dict = None) -> int:
        """
        Increment monthly usage after successful operation.
        
        Args:
            account: Account instance
            limit_type: Type of limit
            amount: Amount to increment
            metadata: Optional metadata
        
        Returns:
            int: New usage amount
        """
        if limit_type not in LimitService.MONTHLY_LIMIT_MAPPINGS:
            raise ValueError(f"Invalid monthly limit type: {limit_type}")
        
        config = LimitService.MONTHLY_LIMIT_MAPPINGS[limit_type]
        usage_field = config['usage_field']
        
        current_usage = getattr(account, usage_field, 0)
        new_usage = current_usage + amount
        setattr(account, usage_field, new_usage)
        account.save(update_fields=[usage_field, 'updated_at'])
        
        logger.info(f"Incremented {limit_type} usage by {amount}. New total: {new_usage}")
        
        return new_usage
    
    @staticmethod
    def get_current_period(account: Account) -> tuple:
        """
        Get current billing period start and end dates from account.
        
        Args:
            account: Account instance
        
        Returns:
            tuple: (period_start, period_end) as datetime objects
        """
        if account.usage_period_start and account.usage_period_end:
            return account.usage_period_start, account.usage_period_end
        
        subscription = getattr(account, 'subscription', None)
        
        if subscription and hasattr(subscription, 'current_period_start'):
            period_start = subscription.current_period_start
            period_end = subscription.current_period_end
        else:
            now = timezone.now()
            period_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
            
            if now.month == 12:
                next_month = now.replace(year=now.year + 1, month=1, day=1)
            else:
                next_month = now.replace(month=now.month + 1, day=1)
            
            period_end = next_month - timedelta(days=1)
            period_end = period_end.replace(hour=23, minute=59, second=59)
        
        account.usage_period_start = period_start
        account.usage_period_end = period_end
        account.save(update_fields=['usage_period_start', 'usage_period_end', 'updated_at'])
        
        return period_start, period_end
    
    @staticmethod
    def get_usage_summary(account: Account) -> dict:
        """
        Get comprehensive usage summary for all limits.
        
        Args:
            account: Account instance
        
        Returns:
            dict: Usage summary with hard and monthly limits
        """
        from django.apps import apps
        
        plan = account.plan
        if not plan:
            return {'error': 'No plan assigned to account'}
        
        period_start, period_end = LimitService.get_current_period(account)
        days_until_reset = (period_end.date() - timezone.now().date()).days if period_end else 0
        
        summary = {
            'account_id': account.id,
            'account_name': account.name,
            'plan_name': plan.name,
            'period_start': period_start.isoformat() if period_start else None,
            'period_end': period_end.isoformat() if period_end else None,
            'days_until_reset': days_until_reset,
            'hard_limits': {},
            'monthly_limits': {},
        }
        
        for limit_type, config in LimitService.HARD_LIMIT_MAPPINGS.items():
            model_path = config['model']
            app_label, model_name = model_path.split('.')
            Model = apps.get_model(app_label, model_name)
            
            filter_field = config.get('filter_field', 'account')
            filter_kwargs = {filter_field: account}
            current_count = Model.objects.filter(**filter_kwargs).count()
            plan_limit = getattr(plan, config['plan_field'])
            
            summary['hard_limits'][limit_type] = {
                'display_name': config['display_name'],
                'current': current_count,
                'limit': plan_limit,
                'remaining': max(0, plan_limit - current_count),
                'percentage_used': int((current_count / plan_limit) * 100) if plan_limit > 0 else 0,
            }
        
        for limit_type, config in LimitService.MONTHLY_LIMIT_MAPPINGS.items():
            plan_limit = getattr(plan, config['plan_field'])
            current_usage = getattr(account, config['usage_field'], 0)
            
            summary['monthly_limits'][limit_type] = {
                'display_name': config['display_name'],
                'current': current_usage,
                'limit': plan_limit,
                'remaining': max(0, plan_limit - current_usage),
                'percentage_used': int((current_usage / plan_limit) * 100) if plan_limit > 0 else 0,
            }
        
        return summary
    
    @staticmethod
    @transaction.atomic
    def reset_monthly_limits(account: Account) -> dict:
        """
        Reset all monthly limits for an account.
        
        Args:
            account: Account instance
        
        Returns:
            dict: Summary of reset operation
        """
        account.usage_content_ideas = 0
        account.usage_content_words = 0
        account.usage_images_basic = 0
        account.usage_images_premium = 0
        account.usage_image_prompts = 0
        
        old_period_end = account.usage_period_end
        
        now = timezone.now()
        new_period_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
        
        if now.month == 12:
            next_month = now.replace(year=now.year + 1, month=1, day=1)
        else:
            next_month = now.replace(month=now.month + 1, day=1)
        
        new_period_end = next_month - timedelta(days=1)
        new_period_end = new_period_end.replace(hour=23, minute=59, second=59)
        
        account.usage_period_start = new_period_start
        account.usage_period_end = new_period_end
        
        account.save(update_fields=[
            'usage_content_ideas', 'usage_content_words', 
            'usage_images_basic', 'usage_images_premium', 'usage_image_prompts',
            'usage_period_start', 'usage_period_end', 'updated_at'
        ])
        
        logger.info(f"Reset monthly limits for account {account.id}")
        
        return {
            'account_id': account.id,
            'old_period_end': old_period_end.isoformat() if old_period_end else None,
            'new_period_start': new_period_start.isoformat(),
            'new_period_end': new_period_end.isoformat(),
            'limits_reset': 5,
        }
