"""
Models for the Referral System.

This module contains models for tracking referrals from different sources (Sales Agents,
IBs, and regular clients), acquisition records, referral events, and rewards.
"""
import logging
import random
import string
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
from shared_models.common.base import BaseModel
from shared_models.transactions.enums import TransactionType, PaymentMethod, PaymentGateway

logger = logging.getLogger(__name__)


def generate_referral_code(source_type, entity_id, campaign=None):
    """
    Generate a unique referral code
    
    Args:
        source_type: 'AGENT', 'IB', or 'CLIENT'
        entity_id: The ID of the user or customer
        campaign: Optional campaign identifier
    
    Returns:
        A unique referral code string
    """
    # Create prefix based on source type
    prefix_map = {
        'AGENT': 'AGT',
        'IB': 'IB',
        'CLIENT': 'CLI',
        'CAMPAIGN': 'CAM',
        'AFFILIATE': 'AFF'
    }
    prefix = prefix_map.get(source_type, 'REF')
    
    # Base code with entity ID
    code = f"{prefix}-{entity_id}"
    
    # Add campaign if provided
    if campaign:
        # Sanitize campaign name (remove spaces, special chars)
        campaign_slug = ''.join(c for c in campaign if c.isalnum()).upper()
        code = f"{code}-{campaign_slug}"
    
    # Add random suffix for uniqueness
    random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))
    code = f"{code}-{random_suffix}"
    
    return code


class ReferralSource(BaseModel):
    """
    Tracks all possible referral sources with their unique codes
    """
    SOURCE_TYPE_CHOICES = [
        ('AGENT', 'Sales Agent'),
        ('IB', 'Introducing Broker'),
        ('CLIENT', 'Regular Client'),
        ('CAMPAIGN', 'Marketing Campaign'),
        ('AFFILIATE', 'Affiliate Partner'),
    ]
    
    source_type = models.CharField(max_length=20, choices=SOURCE_TYPE_CHOICES)
    
    # One of these will be set based on source_type
    crm_user = models.ForeignKey('users.CRMUser', null=True, blank=True,
                                on_delete=models.SET_NULL, related_name='referral_codes')
    customer = models.ForeignKey('customers.Customer', null=True, blank=True,
                               on_delete=models.SET_NULL, related_name='referral_codes')
    
    # Unique code for this referral source
    code = models.CharField(max_length=50, unique=True, db_index=True)
    
    # Optional campaign information
    campaign = models.CharField(max_length=100, blank=True, null=True)
    
    # Optional agreement information for IB-specific referrals
    agreement = models.ForeignKey('ib_commission.IBAgreement', null=True, blank=True,
                                on_delete=models.SET_NULL, related_name='referral_codes')
    
    # Optional target account for rewards
    target_reward_account = models.ForeignKey(
        'accounts.Account', 
        null=True, 
        blank=True,
        on_delete=models.SET_NULL, 
        related_name='reward_referral_sources',
        help_text="Optional: Specific account to receive rewards generated by this code."
    )
    
    # Status & Stats
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    total_referrals = models.IntegerField(default=0)
    converted_referrals = models.IntegerField(default=0) 
    
    class Meta:
        app_label = 'referrals'
        db_table = 'referral_sources'
        verbose_name = 'Referral Source'
        verbose_name_plural = 'Referral Sources'
        constraints = [
            models.CheckConstraint(
                check=(
                    models.Q(source_type='AGENT', crm_user__isnull=False) |
                    models.Q(source_type__in=['IB', 'CLIENT'], customer__isnull=False) |
                    models.Q(source_type__in=['CAMPAIGN', 'AFFILIATE'])
                ),
                name='valid_referral_source_type'
            )
        ]
        
    def save(self, *args, **kwargs):
        if not self.code:
            entity_id = self.crm_user_id if self.source_type == 'AGENT' else self.customer_id
            self.code = generate_referral_code(self.source_type, entity_id, self.campaign)
        super().save(*args, **kwargs)


class AcquisitionRecord(BaseModel):
    """
    Tracks how each customer was acquired, with full referral attribution
    """
    customer = models.OneToOneField('customers.Customer', on_delete=models.CASCADE,
                                  related_name='acquisition')
    
    # Referral information
    referral_source = models.ForeignKey('ReferralSource', null=True, blank=True,
                                      on_delete=models.SET_NULL, related_name='acquisitions')
    referral_code_used = models.CharField(max_length=50, blank=True, null=True)
    
    # Acquisition details
    ACQUISITION_TYPE_CHOICES = [
        ('DIRECT', 'Direct Visit'),
        ('REFERRAL', 'Referral'),
        ('CAMPAIGN', 'Marketing Campaign'),
        ('AFFILIATE', 'Affiliate'),
        ('OTHER', 'Other Source')
    ]
    acquisition_type = models.CharField(max_length=20, choices=ACQUISITION_TYPE_CHOICES, default='DIRECT')
    
    # UTM parameters
    utm_source = models.CharField(max_length=100, blank=True, null=True)
    utm_medium = models.CharField(max_length=100, blank=True, null=True)
    utm_campaign = models.CharField(max_length=100, blank=True, null=True)
    utm_content = models.CharField(max_length=100, blank=True, null=True)
    utm_term = models.CharField(max_length=100, blank=True, null=True)
    
    # Registration channel
    CHANNEL_CHOICES = [
        ('WEB', 'Website'),
        ('MOBILE_APP', 'Mobile App'),
        ('DIRECT_API', 'API Registration'),
        ('MANUAL', 'Manually Created')
    ]
    registration_channel = models.CharField(max_length=20, choices=CHANNEL_CHOICES)
    registration_ip = models.GenericIPAddressField(blank=True, null=True)
    landing_page = models.CharField(max_length=255, blank=True, null=True)
    
    # Journey timestamps
    registration_date = models.DateTimeField(auto_now_add=True)
    kyc_approved_date = models.DateTimeField(blank=True, null=True)
    first_deposit_date = models.DateTimeField(blank=True, null=True)
    first_trade_date = models.DateTimeField(blank=True, null=True)
    
    # Current status
    JOURNEY_STATUS_CHOICES = [
        ('REGISTERED', 'Registered'),
        ('KYC_PENDING', 'KYC Pending'),
        ('KYC_APPROVED', 'KYC Approved'),
        ('FUNDED', 'Account Funded'),
        ('TRADING', 'Actively Trading'),
        ('DORMANT', 'Dormant'),
        ('CHURNED', 'Churned')
    ]
    journey_status = models.CharField(max_length=20, choices=JOURNEY_STATUS_CHOICES, default='REGISTERED')
    
    # For analytics and sales reporting
    assigned_agent = models.ForeignKey('users.CRMUser', null=True, blank=True,
                                     on_delete=models.SET_NULL, related_name='assigned_acquisitions')
    
    class Meta:
        app_label = 'referrals'
        db_table = 'acquisition_records'
        verbose_name = 'Acquisition Record'
        verbose_name_plural = 'Acquisition Records'


class ReferralEvent(BaseModel):
    """
    Tracks significant events in the customer acquisition journey
    """
    EVENT_TYPE_CHOICES = [
        ('REGISTRATION', 'Registration'),
        ('KYC_SUBMISSION', 'KYC Submitted'),
        ('KYC_APPROVED', 'KYC Approved'),
        ('FIRST_DEPOSIT', 'First Deposit'),
        ('FIRST_TRADE', 'First Trade'),
        ('CONVERSION', 'Conversion Milestone'),
        ('IB_ASSIGNMENT', 'IB Assignment'),
        ('AGENT_ASSIGNMENT', 'Agent Assignment')
    ]
    
    acquisition = models.ForeignKey('AcquisitionRecord', on_delete=models.CASCADE, 
                                  related_name='events')
    event_type = models.CharField(max_length=20, choices=EVENT_TYPE_CHOICES)
    timestamp = models.DateTimeField(auto_now_add=True)
    
    # Event-specific data stored as JSON
    data = models.JSONField(default=dict, blank=True)
    
    # For tracking event-based incentives
    is_eligible_for_reward = models.BooleanField(default=False)
    reward_processed = models.BooleanField(default=False)
    reward_amount = models.DecimalField(max_digits=20, decimal_places=5, null=True, blank=True)
    
    class Meta:
        app_label = 'referrals'
        db_table = 'referral_events'
        verbose_name = 'Referral Event'
        verbose_name_plural = 'Referral Events'


class ReferralReward(BaseModel):
    """
    Defines the reward structure for different referral milestones
    """
    REWARD_TYPE_CHOICES = [
        ('FIXED', 'Fixed Amount'),
        ('PERCENTAGE', 'Percentage of Value'),
        ('TIER', 'Tiered Reward'),
        ('CUSTOM', 'Custom Reward')
    ]
    
    SOURCE_TYPE_ELIGIBLE = [
        ('AGENT', 'Sales Agent'),
        ('IB', 'Introducing Broker'),
        ('CLIENT', 'Regular Client'),
        ('AFFILIATE', 'Affiliate Partner'),
        ('ALL', 'All Sources')
    ]
    
    MILESTONE_CHOICES = [
        ('REGISTRATION', 'Registration'),
        ('KYC_APPROVED', 'KYC Approved'),
        ('FIRST_DEPOSIT', 'First Deposit'),
        ('FIRST_TRADE', 'First Trade'),
        ('VOLUME_THRESHOLD', 'Trading Volume'),
        ('TIME_ACTIVE', 'Time as Active Client')
    ]
    
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    
    # Who is eligible
    source_type_eligible = models.CharField(max_length=20, choices=SOURCE_TYPE_ELIGIBLE, default='ALL')
    
    # What triggers the reward
    milestone = models.CharField(max_length=20, choices=MILESTONE_CHOICES)
    
    # Reward details
    reward_type = models.CharField(max_length=20, choices=REWARD_TYPE_CHOICES)
    reward_amount = models.DecimalField(max_digits=20, decimal_places=5, 
                                      help_text="Default reward amount if type is not TIER, or fallback if no tier matches.")
    currency = models.CharField(max_length=3, default='USD')

    # Tier configuration (replaces tier_conditions JSON)
    TIER_BASIS_CHOICES = [
        ('DEPOSIT_AMOUNT', 'First Deposit Amount'),
        ('TRADE_VOLUME', 'First Trade Volume'),
        ('CONVERSION_COUNT', 'Number of Conversions'),
        ('REGISTRATION_COUNT', 'Number of Registrations'),
        # Add other potential bases here
    ]
    tier_basis = models.CharField(
        max_length=20, 
        choices=TIER_BASIS_CHOICES, 
        null=True, 
        blank=True,
        help_text="Basis for tier calculation (e.g., deposit amount). Null if reward_type is not TIER."
    )
    default_reward_type = models.CharField(
        max_length=20, 
        choices=REWARD_TYPE_CHOICES, 
        null=True, 
        blank=True,
        help_text="Default reward type to use if no specific tier matches (optional)."
    )
    default_reward_amount = models.DecimalField(
        max_digits=20, 
        decimal_places=5, 
        null=True, 
        blank=True,
        help_text="Default reward amount to use if no specific tier matches (optional)."
    )

    # Transaction processing details
    transaction_type = models.CharField(
        max_length=20, 
        choices=TransactionType.choices,
        default=TransactionType.BONUS,
        help_text="Type of transaction to create when processing rewards (Bonus, Credit, Deposit, Compensation)"
    )
    
    # Withdrawal requirements (primarily for BONUS type)
    trading_requirement_multiplier = models.DecimalField(
        max_digits=5, decimal_places=2, default=0.00,
        help_text="Multiplier for trading volume requirement before withdrawal (e.g., 5x of bonus amount). 0 means no requirement."
    )
    withdrawal_lock_days = models.IntegerField(
        default=0,
        help_text="Number of days the reward is locked from withdrawal. 0 means no lock."
    )
    
    # Payment method and gateway (optional, mainly for DEPOSIT type)
    payment_method = models.CharField(
        max_length=50, 
        # choices=PaymentMethod.choices,
        blank=True, 
        null=True,
        help_text="Specific payment method to simulate for deposit rewards (optional)."
    )
    payment_gateway = models.CharField(
        max_length=50, 
        # choices=PaymentGateway.choices,
        blank=True, 
        null=True,
        help_text="Specific payment gateway to simulate for deposit rewards (optional)."
    )

    # IB-specific settings & Multi-recipient handling
    reward_ib_directly = models.BooleanField(default=True, 
        help_text="Should the direct IB referrer receive this reward?")
    reward_ib_agent = models.BooleanField(default=False, 
        help_text="Should the IB's assigned agent also receive a share of this reward?")
    
    # Distribution percentages if multiple recipients are involved
    direct_referrer_share = models.DecimalField(
        max_digits=5, decimal_places=2, default=100.00,
        help_text="Percentage share of the reward for the direct referrer (IB/Client/Agent)."
    )
    ib_agent_share = models.DecimalField(
        max_digits=5, decimal_places=2, default=0.00,
        help_text="Percentage share of the reward for the IB's agent (if reward_ib_agent is True)."
    )

    # Constraints
    minimum_deposit = models.DecimalField(max_digits=20, decimal_places=5, default=0,
                                        help_text="Minimum first deposit amount required to trigger this reward.")
    minimum_volume = models.DecimalField(max_digits=20, decimal_places=5, default=0)
    maximum_reward = models.DecimalField(max_digits=20, decimal_places=5, null=True, blank=True)
    
    # Validity
    is_active = models.BooleanField(default=True)
    valid_from = models.DateTimeField(null=True, blank=True)
    valid_until = models.DateTimeField(null=True, blank=True)
    
    class Meta:
        app_label = 'referrals'
        db_table = 'referral_rewards'
        verbose_name = 'Referral Reward'
        verbose_name_plural = 'Referral Rewards'

    def clean(self):
        """ Ensure percentages add up correctly if multiple recipients are enabled. """
        if self.reward_ib_agent:
            total_share = self.direct_referrer_share + self.ib_agent_share
            if total_share != 100.00:
                raise ValidationError(
                    f"If rewarding both IB and Agent, their shares ({self.direct_referrer_share}% + "
                    f"{self.ib_agent_share}%) must add up to 100%. Current total: {total_share}%."
                )
        elif self.direct_referrer_share != 100.00:
             # If only direct referrer, their share must be 100%
             raise ValidationError("Direct referrer share must be 100% if IB's agent is not being rewarded.")
             
        if self.reward_type != 'TIER' and self.tier_basis is not None:
            raise ValidationError("Tier Basis should only be set if Reward Type is TIER.")
            
        if self.reward_type == 'TIER' and self.tier_basis is None:
            raise ValidationError("Tier Basis must be set if Reward Type is TIER.")


class RewardTier(BaseModel):
    """
    Defines individual tiers for a Tiered ReferralReward.
    """
    referral_reward = models.ForeignKey(ReferralReward, on_delete=models.CASCADE, related_name='tiers')
    
    min_value = models.DecimalField(
        max_digits=20, decimal_places=5,
        help_text="Minimum value (inclusive) for this tier based on ReferralReward.tier_basis."
    )
    max_value = models.DecimalField(
        max_digits=20, decimal_places=5, null=True, blank=True,
        help_text="Maximum value (exclusive) for this tier. Null means no upper limit."
    )
    
    reward_type = models.CharField(
        max_length=20, 
        choices=ReferralReward.REWARD_TYPE_CHOICES, # Reuse choices, but likely FIXED or PERCENTAGE
        default='FIXED',
        help_text="Type of reward for this specific tier (e.g., Fixed amount or Percentage)."
    )
    reward_amount = models.DecimalField(
        max_digits=20, decimal_places=5,
        help_text="Reward amount or percentage for this tier."
    )
    
    order = models.PositiveIntegerField(default=0, help_text="Order in which tiers are evaluated.")

    class Meta:
        app_label = 'referrals'
        db_table = 'referral_reward_tiers'
        verbose_name = 'Reward Tier'
        verbose_name_plural = 'Reward Tiers'
        ordering = ['referral_reward', 'order', 'min_value'] # Order by reward config, then explicit order, then min_value

    def clean(self):
        """ Validate tier ranges within the same ReferralReward. """
        super().clean()
        
        # 1. Ensure min_value is less than max_value if max_value is set
        if self.max_value is not None and self.min_value >= self.max_value:
            raise ValidationError(f"Minimum value ({self.min_value}) must be less than Maximum value ({self.max_value}).")

        # 2. Check for overlapping ranges with other tiers of the SAME ReferralReward
        overlapping_tiers = RewardTier.objects.filter(
            referral_reward=self.referral_reward
        ).exclude(pk=self.pk) # Exclude self if updating

        for other_tier in overlapping_tiers:
            # Check for overlap: (self.min < other.max) and (self.max > other.min)
            # Handle null max_values (infinity)
            
            self_max = self.max_value if self.max_value is not None else float('inf')
            other_max = other_tier.max_value if other_tier.max_value is not None else float('inf')
            
            # Overlap condition: !(self ends before other starts OR self starts after other ends)
            # Simplified: (self.min < other.max) and (self.max > other.min)
            
            overlap = (self.min_value < other_max) and (self_max > other_tier.min_value)
            
            if overlap:
                 raise ValidationError(
                    f"Tier range ({self.min_value} - {self.max_value or 'inf'}) overlaps with "
                    f"existing tier ({other_tier.min_value} - {other_tier.max_value or 'inf'}) "
                    f"for the same reward '{self.referral_reward.name}'."
                )

    def __str__(self):
        return (f"Tier for {self.referral_reward.name}: {self.min_value} to "
                f"{self.max_value or 'inf'} -> {self.reward_amount} ({self.reward_type})")


class RewardDistribution(BaseModel):
    """
    Tracks the actual rewards distributed to referrers
    """
    reward = models.ForeignKey('ReferralReward', on_delete=models.PROTECT, 
                             related_name='distributions')
    referral_source = models.ForeignKey('ReferralSource', on_delete=models.PROTECT,
                                      related_name='rewards')
    acquisition = models.ForeignKey('AcquisitionRecord', on_delete=models.PROTECT,
                                  related_name='rewards')
    event = models.ForeignKey('ReferralEvent', null=True, blank=True, on_delete=models.SET_NULL,
                            related_name='rewards')
    
    # Reward recipient (either a customer or CRM user)
    recipient_customer = models.ForeignKey('customers.Customer', null=True, blank=True,
                                         on_delete=models.SET_NULL, 
                                         related_name='referral_rewards')
    recipient_agent = models.ForeignKey('users.CRMUser', null=True, blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='referral_rewards')
    
    # Reward details
    amount = models.DecimalField(max_digits=20, decimal_places=5)
    currency = models.CharField(max_length=3, default='USD')
    
    # Processing status
    PROCESSING_STATUS_CHOICES = [
        ('PENDING', 'Pending'),
        ('PROCESSED', 'Processed'),
        ('FAILED', 'Failed'),
        ('CANCELLED', 'Cancelled')
    ]
    processing_status = models.CharField(max_length=20, choices=PROCESSING_STATUS_CHOICES, default='PENDING')
    processed_date = models.DateTimeField(null=True, blank=True)
    
    # Link to actual transaction if applicable
    transaction = models.ForeignKey('transactions.Transaction', null=True, blank=True,
                                  on_delete=models.SET_NULL, related_name='referral_rewards')
    
    notes = models.TextField(blank=True)
    
    class Meta:
        app_label = 'referrals'
        db_table = 'reward_distributions'
        verbose_name = 'Reward Distribution'
        verbose_name_plural = 'Reward Distributions'
