from __future__ import annotations

from decimal import Decimal
from django.db import transaction
from django.db.utils import OperationalError
import logging
from django.utils import timezone

from apps.accounts.models import User
from apps.business.distributor.models import (
    AutoIDActivationLog,
    AutoIDAllocation,
    DistributorID,
    DistributorOwnershipPeriod,
    DistributorQualificationState,
    DistributorTransferAcceptance,
    DistributorTransferHistory,
    DistributorTransferRequest,
    DistributorTransferTerms,
)


@transaction.atomic
def ensure_current_ownership(distributor: DistributorID) -> DistributorOwnershipPeriod:
    period = DistributorOwnershipPeriod.objects.filter(distributor=distributor, is_current=True).first()
    if period:
        return period

    # Close any stale periods and create a current period aligned to current owner.
    DistributorOwnershipPeriod.objects.filter(distributor=distributor, is_current=True).update(
        is_current=False,
        ownership_effective_to=timezone.now(),
    )
    period = DistributorOwnershipPeriod.objects.create(
        distributor=distributor,
        owner_user=distributor.user,
        ownership_effective_from=timezone.now(),
        is_current=True,
    )
    DistributorQualificationState.objects.get_or_create(
        ownership_period=period,
        defaults={
            'distributor': distributor,
            'owner_user': distributor.user,
            'required_directs': 2,
            'achieved_directs': 0,
            'withdrawal_eligible': False,
        },
    )
    return period


def _count_owner_period_directs(distributor: DistributorID, period: DistributorOwnershipPeriod) -> int:
    qs = DistributorID.objects.filter(sponsor_distributor=distributor)
    qs = qs.filter(created_at__gte=period.ownership_effective_from)
    if period.ownership_effective_to:
        qs = qs.filter(created_at__lt=period.ownership_effective_to)
    return qs.count()


@transaction.atomic
def refresh_qualification_for_distributor(distributor: DistributorID) -> DistributorQualificationState:
    period = ensure_current_ownership(distributor)
    achieved = _count_owner_period_directs(distributor, period)
    eligible = achieved >= 2
    logger = logging.getLogger(__name__)

    try:
        qual, _ = DistributorQualificationState.objects.update_or_create(
            ownership_period=period,
            defaults={
                'distributor': distributor,
                'owner_user': period.owner_user,
                'required_directs': 2,
                'achieved_directs': achieved,
                'withdrawal_eligible': eligible,
                'qualified_at': timezone.now() if eligible else None,
            },
        )

        try:
            DistributorOwnershipPeriod.objects.filter(pk=period.pk).update(
                direct_recruits_count=achieved,
                withdrawal_unlocked=eligible,
                power_stream_activated=achieved >= 3,
            )
        except OperationalError:
            logger.warning('Database locked when updating DistributorOwnershipPeriod for %s; skipping update', period.pk)

        return qual
    except OperationalError:
        # SQLite 'database is locked' can occur under concurrent access. Fall
        # back to a non-writing, in-memory representation so the dashboard
        # summary can still be returned without raising a 500.
        logger.warning('Database locked when updating DistributorQualificationState for distributor %s; returning in-memory result', getattr(distributor, 'id', repr(distributor)))
        try:
            qual = DistributorQualificationState.objects.get(ownership_period=period)
            qual.required_directs = 2
            qual.achieved_directs = achieved
            qual.withdrawal_eligible = eligible
            qual.qualified_at = timezone.now() if eligible else None
        except DistributorQualificationState.DoesNotExist:
            qual = DistributorQualificationState(
                distributor=distributor,
                owner_user=period.owner_user,
                ownership_period=period,
                required_directs=2,
                achieved_directs=achieved,
                withdrawal_eligible=eligible,
                qualified_at=timezone.now() if eligible else None,
            )
        return qual


def get_current_owner_period(distributor: DistributorID) -> DistributorOwnershipPeriod:
    return ensure_current_ownership(distributor)


@transaction.atomic
def submit_transfer_request(*, requested_by: User, target_distributor: DistributorID, reason: str, referral_code: str = '', requested_new_owner_user: User | None = None, accepted_term_ids=None) -> DistributorTransferRequest:
    old_period = ensure_current_ownership(target_distributor)
    new_owner_user = requested_new_owner_user or requested_by
    req = DistributorTransferRequest.objects.create(
        target_distributor=target_distributor,
        old_owner_user=old_period.owner_user,
        requested_new_owner_user=new_owner_user,
        referral_code=referral_code or '',
        reason=reason or '',
    )

    active_terms = DistributorTransferTerms.objects.filter(is_active=True).order_by('display_order', 'created_at')
    accepted_term_ids_set = set(str(term_id) for term_id in (accepted_term_ids or []))
    enforce_selected_terms = accepted_term_ids is not None

    if enforce_selected_terms:
        missing_mandatory_terms = [
            term.title
            for term in active_terms
            if term.mandatory and str(term.id) not in accepted_term_ids_set
        ]
        if missing_mandatory_terms:
            raise ValueError('All mandatory transfer terms must be accepted.')

    for term in active_terms:
        accepted = True
        if enforce_selected_terms:
            accepted = str(term.id) in accepted_term_ids_set
        DistributorTransferAcceptance.objects.create(
            transfer_request=req,
            terms=term,
            accepted_by=requested_by,
            accepted=accepted,
            accepted_at=timezone.now() if accepted else None,
        )

    return req


@transaction.atomic
def approve_transfer_request(*, transfer_request: DistributorTransferRequest, approved_by: User, notes: str = '') -> DistributorTransferHistory:
    if transfer_request.status == DistributorTransferRequest.Status.APPROVED:
        return transfer_request.history

    now = timezone.now()
    distributor = transfer_request.target_distributor
    current_period = ensure_current_ownership(distributor)

    current_period.is_current = False
    current_period.ownership_effective_to = now
    current_period.transfer_reward_boundary_timestamp = now
    current_period.save(update_fields=['is_current', 'ownership_effective_to', 'transfer_reward_boundary_timestamp', 'updated_at'])

    new_period = DistributorOwnershipPeriod.objects.create(
        distributor=distributor,
        owner_user=transfer_request.requested_new_owner_user,
        ownership_effective_from=now,
        transfer_reward_boundary_timestamp=now,
        is_current=True,
        direct_recruits_count=0,
        withdrawal_unlocked=False,
        power_stream_activated=False,
    )

    DistributorQualificationState.objects.create(
        distributor=distributor,
        owner_user=new_period.owner_user,
        ownership_period=new_period,
        required_directs=2,
        achieved_directs=0,
        withdrawal_eligible=False,
    )

    distributor.user = transfer_request.requested_new_owner_user
    distributor.save(update_fields=['user', 'updated_at'])

    transfer_request.status = DistributorTransferRequest.Status.APPROVED
    transfer_request.reviewed_by = approved_by
    transfer_request.reviewed_at = now
    transfer_request.admin_notes = notes or transfer_request.admin_notes
    transfer_request.save(update_fields=['status', 'reviewed_by', 'reviewed_at', 'admin_notes', 'updated_at'])

    history, _ = DistributorTransferHistory.objects.get_or_create(
        transfer_request=transfer_request,
        defaults={
            'distributor': distributor,
            'old_owner_user': transfer_request.old_owner_user,
            'new_owner_user': transfer_request.requested_new_owner_user,
            'approved_at': now,
            'transfer_reward_boundary_timestamp': now,
        },
    )
    return history


@transaction.atomic
def reject_transfer_request(*, transfer_request: DistributorTransferRequest, reviewed_by: User, notes: str = '') -> DistributorTransferRequest:
    transfer_request.status = DistributorTransferRequest.Status.REJECTED
    transfer_request.reviewed_by = reviewed_by
    transfer_request.reviewed_at = timezone.now()
    transfer_request.admin_notes = notes or transfer_request.admin_notes
    transfer_request.save(update_fields=['status', 'reviewed_by', 'reviewed_at', 'admin_notes', 'updated_at'])
    return transfer_request


@transaction.atomic
def activate_auto_ids(*, user: User, allocation: AutoIDAllocation, count: int, product=None):
    if count <= 0:
        raise ValueError('count must be greater than 0')
    if allocation.owner_user_id != user.id:
        raise PermissionError('Allocation does not belong to this user.')
    if allocation.remaining_count < count:
        raise ValueError('Requested auto IDs exceed remaining allocation.')

    from apps.business.distributor.services.distributor_service import create_distributor_id

    source_distributor = allocation.source_distributor
    selected_product = product or allocation.eligible_product or source_distributor.product

    created = []
    total_cost = Decimal('0.00')
    for _ in range(count):
        dist = create_distributor_id(
            user=user,
            product=selected_product,
            sponsor_distributor=source_distributor,
        )
        created.append(dist)
        total_cost += allocation.per_auto_id_value
        AutoIDActivationLog.objects.create(
            allocation=allocation,
            activated_distributor=dist,
            activated_by=user,
            product=selected_product,
            activation_cost=allocation.per_auto_id_value,
        )

    allocation.consumed_count += count
    allocation.remaining_count -= count
    allocation.auto_id_balance = max(Decimal('0.00'), allocation.auto_id_balance - total_cost)
    if allocation.remaining_count <= 0:
        allocation.status = AutoIDAllocation.Status.CONSUMED
    elif allocation.consumed_count > 0:
        allocation.status = AutoIDAllocation.Status.PARTIAL
    allocation.save(update_fields=['consumed_count', 'remaining_count', 'auto_id_balance', 'status', 'updated_at'])

    return {
        'created_count': len(created),
        'total_cost': str(total_cost),
        'distributor_ids': [str(d.id) for d in created],
        'distributor_codes': [d.distributor_code for d in created],
    }
