from apps.business.wallet.services.wallet_service import WalletService
import logging

def process_direct_incentive(distributor_id, distribution_result, reference_id):
    """
    Credits direct incentive to sponsor's wallet with improved safety and robustness.
    Args:
        distributor_id: DistributorID instance
        distribution_result: result from calculate_distribution
        reference_id: ignored, replaced with structured format
    Returns:
        dict: {"success": True, "credited_to": sponsor_user_id, "amount": amount}
    """
    from apps.business.wallet.models import WalletLedger
    from decimal import Decimal, ROUND_HALF_UP
    with transaction.atomic():
        breakdown = distribution_result.get("breakdown", [])
        direct_entry = next((entry for entry in breakdown if entry["type"] == "DIRECT_INCENTIVE"), None)
        if not direct_entry:
            msg = f"No DIRECT_INCENTIVE found for distributor_id={distributor_id.id}"
            logging.error(msg)
            raise Exception(msg)

        sponsor_distributor = getattr(distributor_id, "sponsor_distributor", None)
        if not sponsor_distributor:
            msg = f"No sponsor_distributor for distributor_id={distributor_id.id}"
            logging.error(msg)
            raise Exception(msg)

        sponsor_user = getattr(sponsor_distributor, "user", None)
        if not sponsor_user:
            msg = f"No sponsor_user for sponsor_distributor={sponsor_distributor.id}"
            logging.error(msg)
            raise Exception(msg)

        gross = Decimal(direct_entry["amount"]).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        if gross <= 0:
            msg = f"Direct incentive amount not positive: distributor_id={distributor_id.id}, sponsor_user={sponsor_user.id}, amount={gross}"
            logging.error(msg)
            raise Exception(msg)

        # Apply 25% deduction
        deduction = (gross * Decimal('0.25')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        tds = (deduction * Decimal('0.20')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        repurchase = (deduction * Decimal('0.20')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        tech = (deduction * Decimal('0.20')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        admin = (deduction * Decimal('0.20')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        welfare = (deduction - tds - repurchase - tech - admin).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        net = (gross - deduction).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

        # Prepare full breakdown
        full_breakdown = {
            "gross": str(gross),
            "deduction": str(deduction),
            "tds": str(tds),
            "repurchase": str(repurchase),
            "tech": str(tech),
            "admin": str(admin),
            "welfare": str(welfare),
            "net": str(net)
        }

        base_ref = (reference_id or "NOREF").replace(" ", "")
        structured_reference_id = f"DIRECT_INCENTIVE_{base_ref}_{distributor_id.id}"
        # Duplicate check
        if WalletLedger.objects.filter(reference_id=structured_reference_id, source_type="DIRECT_INCENTIVE").exists():
            msg = f"Duplicate direct incentive: distributor_id={distributor_id.id}, sponsor_user={sponsor_user.id}, reference_id={structured_reference_id}"
            logging.info(msg)
            return {"success": False, "credited_to": sponsor_user.id, "amount": str(net), "reference_id": structured_reference_id, "breakdown": full_breakdown}

        try:
            # Full net goes to main wallet — direct incentive never splits to repurchase
            WalletService.credit_main_wallet(
                user=sponsor_user,
                amount=net,
                source_type="DIRECT_INCENTIVE",
                reference_id=structured_reference_id
            )
        except Exception as e:
            msg = f"Failed to credit wallet: distributor_id={distributor_id.id}, sponsor_user={sponsor_user.id}, amount={net}, reference_id={structured_reference_id}, error={e}"
            logging.error(msg)
            raise Exception(msg)

        log_data = {
            "distributor_id": distributor_id.id,
            "sponsor_user_id": sponsor_user.id,
            "gross": str(gross),
            "net": str(net),
            "reference_id": structured_reference_id,
            "breakdown": full_breakdown
        }
        logging.info(f"Direct incentive credited", extra=log_data)
        return {"success": True, "credited_to": sponsor_user.id, "amount": str(net), "reference_id": structured_reference_id, "breakdown": full_breakdown}
from django.db import transaction
from ..models import DistributorID, DistributorGlobalPositionCounter, distributor_runtime_guard
from uuid import UUID


def _id_mod_100(value):
    if value is None:
        return 0
    if isinstance(value, UUID):
        return value.int % 100
    try:
        return int(value) % 100
    except Exception:
        text = str(value).replace('-', '')
        try:
            return int(text[:8], 16) % 100
        except Exception:
            return 0


def _is_same_scheme(sponsor_distributor, product):
    product_scheme_id = getattr(product, 'opportunity_bundle_id', None)
    sponsor_scheme_id = getattr(getattr(sponsor_distributor, 'product', None), 'opportunity_bundle_id', None)
    if product_scheme_id:
        return sponsor_scheme_id == product_scheme_id
    return getattr(sponsor_distributor, 'product_id', None) == getattr(product, 'id', None)


def _assign_company_global_binary_position(distributor):
    from apps.business.binary_tree.services.binary_service import assign_company_global_binary_position

    root_qs = DistributorID.objects.filter(
        is_company_root=True,
        product__opportunity_bundle_id=getattr(distributor.product, 'opportunity_bundle_id', None),
    ).order_by('created_at', 'id')
    company_root = root_qs.first()

    if company_root is None:
        distributor.is_company_root = True
        distributor.binary_parent_distributor = None
        distributor.binary_position = None
        distributor.binary_level_depth = 0
        distributor.binary_path = 'ROOT'
        distributor.save(update_fields=['is_company_root', 'binary_parent_distributor', 'binary_position', 'binary_level_depth', 'binary_path'])
        return {'placed': True, 'reason': 'root'}

    return assign_company_global_binary_position(distributor, company_root)

# Service: create_distributor_id

def create_distributor_id(user, product, sponsor_distributor):
    """
    Creates a new DistributorID for a user and product, with safe global position and code.
    Also assigns the binary tree position immediately after creation.
    """
    if sponsor_distributor:
        if not _is_same_scheme(sponsor_distributor, product):
            raise Exception("Invalid sponsor distributor: selected distributor ID does not belong to the product opportunity_bundle.")

    global_position = generate_global_position()
    distributor_code = generate_distributor_code(user, product, global_position)
    with distributor_runtime_guard('create_distributor_id:create'):
        distributor = DistributorID.objects.create(
            user=user,
            product=product,
            sponsor_distributor=sponsor_distributor,
            distributor_code=distributor_code,
            global_position=global_position,
            is_active=True
        )

    # Initialize ownership period/qualification state for this distributor.
    try:
        from apps.business.distributor.services.hybrid_service import ensure_current_ownership
        ensure_current_ownership(distributor)
    except Exception as exc:
        import logging as _log
        _log.getLogger(__name__).error(
            "[OWNERSHIP] Failed to initialize ownership period for %s: %s", distributor.id, exc
        )

    # Assign binary placement through the single company-managed BFS tree.
    try:
        with distributor_runtime_guard('create_distributor_id:binary-placement'):
            _assign_company_global_binary_position(distributor)
    except Exception as exc:
        import logging as _log
        _log.getLogger(__name__).error(
            "[BINARY] Failed to assign binary position for %s: %s", distributor.id, exc
        )

    return distributor

# Service: generate_global_position

def generate_global_position():
    with transaction.atomic():
        counter, _ = DistributorGlobalPositionCounter.objects.select_for_update().get_or_create(pk=1)
        counter.counter += 1
        counter.save()
        return counter.counter

# Service: generate_distributor_code

def generate_distributor_code(user, product, global_position):
    scheme_hint = _id_mod_100(getattr(product, 'opportunity_bundle_id', None))
    product_hint = _id_mod_100(getattr(product, 'id', None))
    position_part = int(global_position) % 100000000
    mobile = (getattr(user, 'mobile', '') or '')
    mobile_suffix = mobile[-2:] if len(mobile) >= 2 else '00'
    return f"TW{scheme_hint:02d}{product_hint:02d}{position_part:08d}{mobile_suffix}"
