from decimal import Decimal
from expat_life.models import PlatformFeeSettings
from django.utils import timezone
from .models import AffiliateProfile, AffiliateGuestReferral, AffiliateCommission
from django.db.models import Sum

import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer
from io import BytesIO
from django.core.files.base import ContentFile
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
import os

from django.urls import reverse
from django.contrib.sites.shortcuts import get_current_site

def get_platform_fee_settings():
    settings_obj = PlatformFeeSettings.objects.filter(is_active=True).first()
    if settings_obj:
        return settings_obj

    return PlatformFeeSettings.objects.first()


def get_affiliate_commission_percentage():
    settings_obj = get_platform_fee_settings()

    if settings_obj and hasattr(settings_obj, 'affiliate_commission_percentage'):
        return settings_obj.affiliate_commission_percentage

    return Decimal('20.00')


def calculate_affiliate_commission(amount):
    percentage = get_affiliate_commission_percentage()

    if amount is None:
        amount = Decimal('0.00')

    amount = Decimal(amount)
    return (amount * percentage / Decimal('100')).quantize(Decimal('0.01'))

def create_affiliate_referral_from_signup(request, user, lead=None, profile=None):
    affiliate_id = request.session.get('affiliate_id')
    affiliate_code = (
        request.session.get('affiliate_code')
        or request.COOKIES.get('affiliate_code')
    )

    affiliate = None

    if affiliate_id:
        affiliate = AffiliateProfile.objects.filter(
            id=affiliate_id,
            status='approved',
            is_active=True
        ).first()

    if not affiliate and affiliate_code:
        affiliate = AffiliateProfile.objects.filter(
            affiliate_code__iexact=affiliate_code,
            status='approved',
            is_active=True
        ).first()

    if not affiliate:
        return None

    full_name = ''
    email = ''
    phone = ''
    country = ''

    if lead:
        full_name = getattr(lead, 'full_name', '') or ''
        email = getattr(lead, 'email', '') or ''
        phone = getattr(lead, 'whatsapp_number', '') or ''
        country = getattr(lead, 'nationality', '') or ''

    # --- Package-based commission calculation ---
    interested_package = getattr(lead, 'interested_package', None) if lead else None

    selected_package_name = ''
    selected_package_amount = Decimal('0.00')
    possible_confirmation_commission = Decimal('0.00')
    possible_service_fee_commission = Decimal('0.00')

    if interested_package:
        selected_package_name = interested_package.name
        selected_package_amount = interested_package.monthly_price_usd or Decimal('0.00')

        # confirmation commission: % of package's confirmation_fee_usd
        possible_confirmation_commission = calculate_affiliate_commission(
            interested_package.confirmation_fee_usd
        )

        # service fee commission: already computed as a property on Package
        # Package.affiliate_fee_amount = service_fee_amount * affiliate_fee_percentage / 100
        possible_service_fee_commission = interested_package.affiliate_fee_amount

    else:
        # No package selected — fall back to platform settings confirmation fee only
        settings_obj = get_platform_fee_settings()
        confirmation_fee = Decimal('0.00')
        if settings_obj:
            # PlatformFeeSettings doesn't have confirmation fee — use global commission % only
            # We have no base amount without a package, so leave as 0
            pass
        possible_confirmation_commission = Decimal('0.00')
        possible_service_fee_commission = Decimal('0.00')

    total_possible = possible_confirmation_commission + possible_service_fee_commission

    referral, created = AffiliateGuestReferral.objects.get_or_create(
        affiliate=affiliate,
        guest_user=user,
        defaults={
            'foreigner_lead': lead,
            'foreigner_profile': profile,
            'guest_name': full_name,
            'guest_email': email,
            'guest_phone': phone,
            'guest_country': country,
            'selected_package_name': selected_package_name,
            'selected_package_amount_usd': selected_package_amount,
            'possible_confirmation_commission_usd': possible_confirmation_commission,
            'possible_service_fee_commission_usd': possible_service_fee_commission,
            'total_possible_commission_usd': total_possible,
            'status': 'signed_up',
        }
    )

    if not created:
        referral.foreigner_lead = lead or referral.foreigner_lead
        referral.foreigner_profile = profile or referral.foreigner_profile
        referral.guest_name = full_name or referral.guest_name
        referral.guest_email = email or referral.guest_email
        referral.guest_phone = phone or referral.guest_phone
        referral.guest_country = country or referral.guest_country
        referral.selected_package_name = selected_package_name or referral.selected_package_name
        referral.selected_package_amount_usd = selected_package_amount or referral.selected_package_amount_usd
        referral.possible_confirmation_commission_usd = possible_confirmation_commission or referral.possible_confirmation_commission_usd
        referral.possible_service_fee_commission_usd = possible_service_fee_commission or referral.possible_service_fee_commission_usd
        referral.total_possible_commission_usd = total_possible or referral.total_possible_commission_usd
        referral.save()

    return referral

def handle_affiliate_confirmation_fee_paid(user):
    """
    Call this when a guest's confirmation fee payment is marked as paid.
    - Updates referral status to 'confirmed'
    - Creates or updates AffiliateCommission for confirmation fee
    - Updates affiliate's total_earned_usd
    """
    referral = AffiliateGuestReferral.objects.filter(
        guest_user=user,
        status='signed_up'
    ).select_related('affiliate').first()

    if not referral:
        return None

    settings_obj = get_platform_fee_settings()
    confirmation_fee = Decimal('0.00')
    if settings_obj and hasattr(settings_obj, 'foreigner_confirmation_fee_usd'):
        confirmation_fee = settings_obj.foreigner_confirmation_fee_usd or Decimal('0.00')

    commission_amount = calculate_affiliate_commission(confirmation_fee)
    percentage = get_affiliate_commission_percentage()

    commission, created = AffiliateCommission.objects.get_or_create(
        referral=referral,
        commission_for='guest_confirmation_fee',
        defaults={
            'affiliate': referral.affiliate,
            'source_amount_usd': confirmation_fee,
            'commission_percentage': percentage,
            'commission_amount_usd': commission_amount,
            'status': 'earned',
            'earned_at': timezone.now(),
        }
    )

    if not created and commission.status != 'earned':
        commission.source_amount_usd = confirmation_fee
        commission.commission_percentage = percentage
        commission.commission_amount_usd = commission_amount
        commission.status = 'earned'
        commission.earned_at = timezone.now()
        commission.save()

    # Update referral
    referral.status = 'confirmed'
    referral.possible_confirmation_commission_usd = commission_amount
    referral.total_earned_commission_usd = (
        referral.total_earned_commission_usd + commission_amount
    )
    referral.save()

    # Update affiliate total earned
    affiliate = referral.affiliate
    affiliate.total_earned_usd = (
        AffiliateCommission.objects.filter(
            affiliate=affiliate,
            status='earned'
        ).aggregate(total=Sum('commission_amount_usd'))['total'] or Decimal('0.00')
    )
    affiliate.save(update_fields=['total_earned_usd'])

    return commission


def handle_affiliate_service_fee_paid(user, service_fee_amount):
    """
    Call this when a guest's one-time service fee payment is marked as paid.
    - Updates referral status to 'service_fee_paid'
    - Creates or updates AffiliateCommission for service fee
    - Updates affiliate's total_earned_usd
    """
    referral = AffiliateGuestReferral.objects.filter(
        guest_user=user,
        status__in=['signed_up', 'confirmed']
    ).select_related('affiliate').first()

    if not referral:
        return None

    service_fee_amount = Decimal(service_fee_amount)
    commission_amount = calculate_affiliate_commission(service_fee_amount)
    percentage = get_affiliate_commission_percentage()

    commission, created = AffiliateCommission.objects.get_or_create(
        referral=referral,
        commission_for='guest_one_time_service_fee',
        defaults={
            'affiliate': referral.affiliate,
            'source_amount_usd': service_fee_amount,
            'commission_percentage': percentage,
            'commission_amount_usd': commission_amount,
            'status': 'earned',
            'earned_at': timezone.now(),
        }
    )

    if not created and commission.status != 'earned':
        commission.source_amount_usd = service_fee_amount
        commission.commission_percentage = percentage
        commission.commission_amount_usd = commission_amount
        commission.status = 'earned'
        commission.earned_at = timezone.now()
        commission.save()

    # Update referral
    referral.status = 'service_fee_paid'
    referral.possible_service_fee_commission_usd = commission_amount
    referral.total_possible_commission_usd = (
        referral.possible_confirmation_commission_usd + commission_amount
    )
    referral.total_earned_commission_usd = (
        referral.total_earned_commission_usd + commission_amount
    )
    referral.save()

    # Update affiliate total earned
    affiliate = referral.affiliate
    affiliate.total_earned_usd = (
        AffiliateCommission.objects.filter(
            affiliate=affiliate,
            status='earned'
        ).aggregate(total=Sum('commission_amount_usd'))['total'] or Decimal('0.00')
    )
    affiliate.save(update_fields=['total_earned_usd'])

    return commission

def generate_qr_code_image(link: str, size: int = 300, border: int = 2) -> BytesIO:
    """
    Generate a QR code image from the given link and return a BytesIO buffer.

    Args:
        link: URL to encode (e.g., 'https://example.com/?ref=CODE123')
        size: Width/height of the QR code in pixels (default 300)
        border: Border size in modules (default 2)

    Returns:
        BytesIO containing the PNG image data.
    """
    qr = qrcode.QRCode(
        version=None,  # auto-size
        error_correction=qrcode.constants.ERROR_CORRECT_M,
        box_size=10,
        border=border,
    )
    qr.add_data(link)
    qr.make(fit=True)

    # You can style the QR code – for a clean look we use standard plain PIL
    img = qr.make_image(fill_color="black", back_color="white").convert('RGB')
    
    # Resize to desired pixel dimensions
    img = img.resize((size, size), Image.Resampling.LANCZOS)
    
    # Save to BytesIO
    buffer = BytesIO()
    img.save(buffer, format='PNG')
    buffer.seek(0)
    return buffer

PROMOTIONAL_LINKS = [
    ('expat_life:home', 'Home Page'),
    ('expat_life:foreigner_signup', 'Foreigner Signup'),
    ('expat_life:about_sri_lanka', 'About Sri Lanka'),
    ('expat_life:sri_lanka_digital_nomad_visa', 'Digital Nomad Visa'),
    ('expat_life:package_list', 'Packages'),
    ('expat_life:rental_home', 'Rentals'),
    ('expat_life:dating_home', 'Dating Services'),
    ('expat_life:service_partner_home', 'Assistants & Partners'),
]

def build_referral_url(request, url_name, affiliate_code):
    """
    Constructs absolute URL for a named view with ?ref=affiliate_code.
    Example: https://yourdomain.com/packages/?ref=CODE123
    """
    path = reverse(url_name)  # e.g., '/packages/'
    current_site = get_current_site(request)
    protocol = 'https' if request.is_secure() else 'http'
    domain = current_site.domain
    base_url = f"{protocol}://{domain}{path}"
    return f"{base_url}?ref={affiliate_code}"



def abbreviate_name(full_name, max_length=30):
    """
    Abbreviate long names from the left while keeping the last parts full.
    Useful for Sri Lankan names where the first few parts are initials
    and the latter parts are the actual calling name.

    Examples:
        "Don Sugath Wasantha Jayathunge" -> "D. Sugath Wasantha Jayathunge" (if within limit)
        Still too long? -> "D. S. Wasantha Jayathunge"
        Extremely long? -> "D. S. W. Jayathunge" (but keeps last two or three words full)
    
    The function never abbreviates the last word (surname).
    """
    words = full_name.strip().split()
    if len(words) <= 1:
        # Single word: just truncate if necessary
        return full_name[:max_length] if len(full_name) > max_length else full_name

    # Keep the last word always full, abbreviate others from the left
    # We'll store the current state: for each word index, whether it's abbreviated
    is_abbrev = [False] * len(words)
    # Last word never abbreviated
    is_abbrev[-1] = False

    def current_length():
        # Calculate current string length with dots and spaces
        result = []
        for idx, word in enumerate(words):
            if is_abbrev[idx]:
                result.append(word[0] + '.')
            else:
                result.append(word)
        return len(' '.join(result))

    # While length exceeds max and we have non-last words that can be abbreviated
    while current_length() > max_length:
        # Find the leftmost unabbreviated word that is not the last
        target_idx = -1
        for i in range(len(words) - 1):  # exclude last word
            if not is_abbrev[i]:
                target_idx = i
                break
        if target_idx == -1:
            # No more words to abbreviate, break to avoid infinite loop
            break
        # Abbreviate this word
        is_abbrev[target_idx] = True

    # Build the final abbreviated name
    final_parts = []
    for idx, word in enumerate(words):
        if is_abbrev[idx]:
            final_parts.append(word[0] + '.')
        else:
            final_parts.append(word)
    result = ' '.join(final_parts)
    
    # If still somehow too long (rare), truncate the whole string
    if len(result) > max_length:
        result = result[:max_length]
    return result


def generate_business_card_composite(
    affiliate_profile,
    qr_image_buffer,
    background_image_path,
    card_size=(1050, 600),
    name_font_path=None,       # e.g., Poppins-Bold.ttf
    contact_font_path=None,    # e.g., Poppins-Regular.ttf
    code_font_path=None,       # e.g., Poppins-Medium.ttf
    # Optional fine‑tuning parameters
    qr_vertical_offset=40,     # positive = move QR down
):
    """
    Generate a business card composite with exact Photoshop coordinates:
    Name: 48px at (50, 100)
    Contact: 24px at (100, 400)
    Email: 24px at (100, 430)
    Affiliate code: 24px at (100, 460)
    QR: 2/3 card height, right margin 40, vertically adjusted by offset.
    """
    # 1. Load background
    if not os.path.exists(background_image_path):
        raise FileNotFoundError(f"Background image not found: {background_image_path}")
    bg = Image.open(background_image_path).convert("RGB")
    bg = bg.resize(card_size, Image.Resampling.LANCZOS)
    card = bg.convert("RGBA")

    # 2. Load fonts (with explicit sizes)
    name_font = None
    contact_font = None
    code_font = None

    if name_font_path and os.path.exists(name_font_path):
        name_font = ImageFont.truetype(name_font_path, 48)   # Photoshop size
    else:
        raise FileNotFoundError(f"Name font not found at {name_font_path}")

    if contact_font_path and os.path.exists(contact_font_path):
        contact_font = ImageFont.truetype(contact_font_path, 24)
    else:
        raise FileNotFoundError(f"Contact font not found at {contact_font_path}")

    if code_font_path and os.path.exists(code_font_path):
        code_font = ImageFont.truetype(code_font_path, 10)
    else:
        raise FileNotFoundError(f"Code font not found at {code_font_path}")

    draw = ImageDraw.Draw(card)

    # 3. Draw name (exact coordinates from Photoshop)
    display_name = abbreviate_name(affiliate_profile.full_name, max_length=30)

    # Draw name
    name_x = 60
    name_y = 60
    draw.text((name_x, name_y), display_name, font=name_font, fill=(33, 37, 41))

    # 4. Draw contact number (WhatsApp or phone)
    contact_no = affiliate_profile.whatsapp or affiliate_profile.phone or "No contact number"
    contact_x = 125
    contact_y = 380
    draw.text((contact_x, contact_y), contact_no, font=contact_font, fill=(73, 80, 87))

    # 5. Draw email
    email = affiliate_profile.user.email
    email_y = contact_y + 60   # 30px spacing (adjust as needed)
    draw.text((contact_x, email_y), email, font=contact_font, fill=(73, 80, 87))

    # 6. Draw affiliate code
    code_text = affiliate_profile.affiliate_code
    code_y = email_y + 113     # 30px spacing
    draw.text((name_x, code_y), code_text, font=code_font, fill=(33, 37, 41))

    # 7. Draw QR code (right side, 2/3 card height)
    qr_img = Image.open(qr_image_buffer).convert("RGBA")
    card_height = card_size[1]
    target_height = int(card_height * 2 / 3)
    aspect = qr_img.width / qr_img.height
    target_width = int(target_height * aspect)
    qr_img = qr_img.resize((target_width, target_height), Image.Resampling.LANCZOS)

    right_margin = 40
    qr_x = card_size[0] - target_width - right_margin
    qr_y = (card_height - target_height) // 2 + qr_vertical_offset
    card.paste(qr_img, (qr_x, qr_y), qr_img)

    # 8. Output
    output_buffer = BytesIO()
    card.save(output_buffer, format='PNG')
    output_buffer.seek(0)
    return output_buffer