# truewave-platform\backend\apps\business\distributor\models.py
import logging
from contextlib import contextmanager
from contextvars import ContextVar

from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.db.models import Q
from apps.core.models import BaseModel
from apps.accounts.models import User
from apps.business.products.models import Product
from apps.business.schemes.models import OpportunityBundle
from decimal import Decimal


logger = logging.getLogger(__name__)

_DISTRIBUTOR_RUNTIME_GUARD = ContextVar('distributor_runtime_guard', default='')
_BINARY_GUARDED_UPDATE_FIELDS = {
    'binary_parent_distributor',
    'binary_parent_distributor_id',
    'binary_position',
    'binary_level_depth',
    'binary_path',
    'is_company_root',
}


def distributor_runtime_guard_active():
    return bool(_DISTRIBUTOR_RUNTIME_GUARD.get())


@contextmanager
def distributor_runtime_guard(reason):
    token = _DISTRIBUTOR_RUNTIME_GUARD.set(reason or 'unspecified')
    try:
        yield
    finally:
        _DISTRIBUTOR_RUNTIME_GUARD.reset(token)


def ensure_distributor_runtime_guard(action, fields=None):
    reason = _DISTRIBUTOR_RUNTIME_GUARD.get()
    if reason:
        return

    field_text = ''
    if fields:
        field_text = f" Fields: {', '.join(sorted(fields))}."
    message = (
        f"Distributor runtime guard violation during {action}."
        f"{field_text} Use create_distributor_id() and company-root BFS placement only."
    )
    logger.warning(message)
    raise ValidationError(message)


class DistributorIDQuerySet(models.QuerySet):
    def create(self, **kwargs):
        ensure_distributor_runtime_guard('DistributorID.objects.create')
        return super().create(**kwargs)

    def bulk_create(self, objs, **kwargs):
        ensure_distributor_runtime_guard('DistributorID.objects.bulk_create')
        return super().bulk_create(objs, **kwargs)

    def update(self, **kwargs):
        guarded_fields = _BINARY_GUARDED_UPDATE_FIELDS.intersection(kwargs.keys())
        if guarded_fields:
            ensure_distributor_runtime_guard('DistributorID.objects.update', fields=guarded_fields)
        return super().update(**kwargs)


DistributorIDManager = models.Manager.from_queryset(DistributorIDQuerySet)

class DistributorID(BaseModel):
    class BinaryPosition(models.TextChoices):
        LEFT = 'LEFT', 'Left'
        RIGHT = 'RIGHT', 'Right'

    user = models.ForeignKey(User, related_name='distributor_ids', on_delete=models.CASCADE, db_index=True)
    sponsor_distributor = models.ForeignKey('self', null=True, blank=True, related_name='direct_referrals', on_delete=models.SET_NULL, db_index=True)
    # Binary tree placement (set after first 2 direct referrals; BFS spillover after that)
    binary_parent_distributor = models.ForeignKey(
        'self', null=True, blank=True, related_name='binary_children',
        on_delete=models.SET_NULL, db_index=True, verbose_name='Binary Parent',
    )
    binary_position = models.CharField(
        max_length=5, choices=BinaryPosition.choices, null=True, blank=True, db_index=True,
        verbose_name='Binary Position',
    )
    binary_level_depth = models.PositiveIntegerField(default=0, db_index=True)
    binary_path = models.CharField(max_length=1024, blank=True, default='', db_index=True)
    binary_subtree_filled_count = models.PositiveIntegerField(default=0)
    is_company_root = models.BooleanField(default=False, db_index=True)
    product = models.ForeignKey(Product, on_delete=models.CASCADE, db_index=True)
    distributor_code = models.CharField(max_length=16, unique=True, db_index=True)
    global_position = models.PositiveIntegerField(unique=True, db_index=True)
    is_active = models.BooleanField(default=False)
    is_auto_generated = models.BooleanField(default=False)
    objects = DistributorIDManager()

    def __str__(self):
        return f"{self.distributor_code} ({self.user_id})"

    def save(self, *args, **kwargs):
        if self._state.adding:
            ensure_distributor_runtime_guard('DistributorID.save(create)')
        else:
            current = type(self).objects.filter(pk=self.pk).values(
                'binary_parent_distributor_id',
                'binary_position',
                'binary_level_depth',
                'binary_path',
                'is_company_root',
            ).first()
            if current is not None:
                changed_fields = []
                if current['binary_parent_distributor_id'] != self.binary_parent_distributor_id:
                    changed_fields.append('binary_parent_distributor_id')
                if current['binary_position'] != self.binary_position:
                    changed_fields.append('binary_position')
                if current['binary_level_depth'] != self.binary_level_depth:
                    changed_fields.append('binary_level_depth')
                if current['binary_path'] != self.binary_path:
                    changed_fields.append('binary_path')
                if current['is_company_root'] != self.is_company_root:
                    changed_fields.append('is_company_root')
                if changed_fields:
                    ensure_distributor_runtime_guard('DistributorID.save(update)', fields=changed_fields)

        return super().save(*args, **kwargs)

    def get_binary_left_child(self):
        return self.binary_children.filter(binary_position=self.BinaryPosition.LEFT).first()

    def get_binary_right_child(self):
        return self.binary_children.filter(binary_position=self.BinaryPosition.RIGHT).first()

    class Meta:
        indexes = [
            models.Index(fields=["user"]),
            models.Index(fields=["sponsor_distributor"]),
            models.Index(fields=["global_position"]),
            models.Index(fields=["binary_parent_distributor", "binary_position"]),
        ]
        constraints = [
            models.UniqueConstraint(
                fields=['binary_parent_distributor', 'binary_position'],
                condition=Q(binary_parent_distributor__isnull=False, binary_position__isnull=False),
                name='uniq_binary_parent_position',
            ),
        ]

class DistributorGlobalPositionCounter(BaseModel):
    counter = models.PositiveIntegerField(default=0)
    updated_at = models.DateTimeField(auto_now=True)

    def save(self, *args, **kwargs):
        self.pk = 1  # enforce singleton
        super().save(*args, **kwargs)

    class Meta:
        verbose_name = "Distributor Global Position Counter"
        verbose_name_plural = "Distributor Global Position Counters"


class BrandAmbassadorEligibility(BaseModel):
    user = models.ForeignKey(User, related_name='ambassador_eligibilities', on_delete=models.CASCADE, db_index=True)
    opportunity_bundle = models.ForeignKey(OpportunityBundle, related_name='ambassador_eligibilities', on_delete=models.CASCADE, db_index=True, null=True, blank=True)
    distributor_count = models.PositiveIntegerField(default=0)
    qualified_at = models.DateTimeField(auto_now_add=True)
    last_distributor_created_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['user', 'opportunity_bundle'], name='uniq_ambassador_user_opportunity_bundle'),
        ]
        indexes = [
            models.Index(fields=['opportunity_bundle', 'distributor_count']),
            models.Index(fields=['user', 'opportunity_bundle']),
            models.Index(fields=['is_active']),
        ]

    def __str__(self):
        return f"{self.user_id} @ {self.opportunity_bundle_id} ({self.distributor_count})"


class DistributorOwnershipPeriod(BaseModel):
    distributor = models.ForeignKey(DistributorID, related_name='ownership_periods', on_delete=models.CASCADE, db_index=True)
    owner_user = models.ForeignKey(User, related_name='owned_distributor_periods', on_delete=models.CASCADE, db_index=True)
    ownership_effective_from = models.DateTimeField(db_index=True)
    ownership_effective_to = models.DateTimeField(null=True, blank=True, db_index=True)
    transfer_reward_boundary_timestamp = models.DateTimeField(null=True, blank=True, db_index=True)
    is_current = models.BooleanField(default=True, db_index=True)
    direct_recruits_count = models.PositiveIntegerField(default=0)
    withdrawal_unlocked = models.BooleanField(default=False, db_index=True)
    power_stream_activated = models.BooleanField(default=False, db_index=True)

    class Meta:
        indexes = [
            models.Index(fields=['distributor', 'is_current']),
            models.Index(fields=['owner_user', 'is_current']),
            models.Index(fields=['ownership_effective_from', 'ownership_effective_to']),
        ]


class DistributorQualificationState(BaseModel):
    distributor = models.ForeignKey(DistributorID, related_name='qualification_states', on_delete=models.CASCADE, db_index=True)
    owner_user = models.ForeignKey(User, related_name='distributor_qualification_states', on_delete=models.CASCADE, db_index=True)
    ownership_period = models.ForeignKey(DistributorOwnershipPeriod, related_name='qualification_states', on_delete=models.CASCADE, db_index=True)
    required_directs = models.PositiveIntegerField(default=2)
    achieved_directs = models.PositiveIntegerField(default=0)
    withdrawal_eligible = models.BooleanField(default=False, db_index=True)
    qualified_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        indexes = [
            models.Index(fields=['distributor', 'owner_user']),
            models.Index(fields=['withdrawal_eligible']),
        ]
        constraints = [
            models.UniqueConstraint(fields=['ownership_period'], name='uniq_qualification_per_ownership_period'),
        ]


class DistributorTransferTerms(BaseModel):
    title = models.CharField(max_length=255)
    body = models.TextField()
    display_order = models.PositiveIntegerField(default=1)
    mandatory = models.BooleanField(default=True)
    applies_from = models.DateTimeField(null=True, blank=True)
    applies_to = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ['display_order', 'created_at']


class DistributorTransferRequest(BaseModel):
    class Status(models.TextChoices):
        SUBMITTED = 'SUBMITTED', 'Submitted'
        OLD_OWNER_CONFIRMED = 'OLD_OWNER_CONFIRMED', 'Old Owner Confirmed'
        APPROVED = 'APPROVED', 'Approved'
        REJECTED = 'REJECTED', 'Rejected'
        CANCELLED = 'CANCELLED', 'Cancelled'

    target_distributor = models.ForeignKey(DistributorID, related_name='transfer_requests', on_delete=models.CASCADE, db_index=True)
    old_owner_user = models.ForeignKey(User, related_name='transfer_requests_as_old_owner', on_delete=models.CASCADE)
    requested_new_owner_user = models.ForeignKey(User, related_name='transfer_requests_as_new_owner', on_delete=models.CASCADE)
    referral_code = models.CharField(max_length=64, blank=True, default='')
    reason = models.TextField(blank=True, default='')
    status = models.CharField(max_length=24, choices=Status.choices, default=Status.SUBMITTED, db_index=True)
    reviewed_by = models.ForeignKey(User, null=True, blank=True, related_name='reviewed_transfer_requests', on_delete=models.SET_NULL)
    reviewed_at = models.DateTimeField(null=True, blank=True)
    admin_notes = models.TextField(blank=True, default='')

    class Meta:
        indexes = [
            models.Index(fields=['target_distributor', 'status']),
            models.Index(fields=['requested_new_owner_user', 'status']),
            models.Index(fields=['old_owner_user', 'status']),
        ]


class DistributorTransferAcceptance(BaseModel):
    transfer_request = models.ForeignKey(DistributorTransferRequest, related_name='acceptances', on_delete=models.CASCADE, db_index=True)
    terms = models.ForeignKey(DistributorTransferTerms, related_name='acceptances', on_delete=models.CASCADE)
    accepted_by = models.ForeignKey(User, related_name='accepted_transfer_terms', on_delete=models.CASCADE)
    accepted = models.BooleanField(default=False)
    accepted_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        indexes = [
            models.Index(fields=['transfer_request', 'accepted_by']),
        ]
        constraints = [
            models.UniqueConstraint(fields=['transfer_request', 'terms', 'accepted_by'], name='uniq_transfer_term_acceptance'),
        ]


class DistributorTransferHistory(BaseModel):
    transfer_request = models.OneToOneField(DistributorTransferRequest, related_name='history', on_delete=models.CASCADE)
    distributor = models.ForeignKey(DistributorID, related_name='transfer_histories', on_delete=models.CASCADE, db_index=True)
    old_owner_user = models.ForeignKey(User, related_name='transfer_histories_as_old_owner', on_delete=models.CASCADE)
    new_owner_user = models.ForeignKey(User, related_name='transfer_histories_as_new_owner', on_delete=models.CASCADE)
    approved_at = models.DateTimeField(db_index=True)
    transfer_reward_boundary_timestamp = models.DateTimeField(db_index=True)


class DistributorRewardOwnershipLedger(BaseModel):
    class RewardType(models.TextChoices):
        SPONSOR_INCOME = 'SPONSOR_INCOME', 'Sponsor Income'
        POWER_STREAM = 'POWER_STREAM', 'Power Stream'
        LEVEL_REWARD = 'LEVEL_REWARD', 'Level Reward'
        WITHDRAWAL = 'WITHDRAWAL', 'Withdrawal'

    distributor = models.ForeignKey(DistributorID, related_name='reward_ownership_ledger', on_delete=models.CASCADE, db_index=True)
    beneficiary_user = models.ForeignKey(User, related_name='distributor_reward_ownership_entries', on_delete=models.CASCADE, db_index=True)
    ownership_period = models.ForeignKey(DistributorOwnershipPeriod, related_name='reward_entries', on_delete=models.CASCADE, db_index=True)
    reward_type = models.CharField(max_length=24, choices=RewardType.choices, db_index=True)
    reward_reference = models.CharField(max_length=128, db_index=True)
    amount = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal('0.00'))
    reward_timestamp = models.DateTimeField(db_index=True)
    metadata = models.JSONField(null=True, blank=True)

    class Meta:
        indexes = [
            models.Index(fields=['distributor', 'reward_timestamp']),
            models.Index(fields=['beneficiary_user', 'reward_timestamp']),
            models.Index(fields=['reward_type', 'reward_timestamp']),
        ]


class AutoIDAllocation(BaseModel):
    class Status(models.TextChoices):
        PENDING = 'PENDING', 'Pending'
        PARTIAL = 'PARTIAL', 'Partially Consumed'
        CONSUMED = 'CONSUMED', 'Consumed'

    owner_user = models.ForeignKey(User, related_name='auto_id_allocations', on_delete=models.CASCADE, db_index=True)
    source_distributor = models.ForeignKey(DistributorID, related_name='auto_id_allocations', on_delete=models.CASCADE, db_index=True)
    level_number = models.PositiveIntegerField(db_index=True)
    total_count = models.PositiveIntegerField(default=0)
    consumed_count = models.PositiveIntegerField(default=0)
    remaining_count = models.PositiveIntegerField(default=0, db_index=True)
    auto_id_balance = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal('0.00'))
    per_auto_id_value = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal('0.00'))
    eligible_product = models.ForeignKey(Product, null=True, blank=True, related_name='auto_id_allocations', on_delete=models.SET_NULL)
    status = models.CharField(max_length=16, choices=Status.choices, default=Status.PENDING, db_index=True)

    class Meta:
        indexes = [
            models.Index(fields=['owner_user', 'status']),
            models.Index(fields=['source_distributor', 'status']),
        ]


class AutoIDActivationLog(BaseModel):
    allocation = models.ForeignKey(AutoIDAllocation, related_name='activation_logs', on_delete=models.CASCADE, db_index=True)
    activated_distributor = models.ForeignKey(DistributorID, related_name='auto_id_activation_logs', on_delete=models.CASCADE)
    activated_by = models.ForeignKey(User, related_name='auto_id_activation_logs', on_delete=models.CASCADE)
    product = models.ForeignKey(Product, related_name='auto_id_activation_logs', on_delete=models.CASCADE)
    activation_cost = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal('0.00'))
    notes = models.TextField(blank=True, default='')
