from decimal import Decimal, ROUND_HALF_UP
from django.utils import timezone
from django.db import transaction

from expat_life.utils import (
    get_platform_fee_settings,
    create_or_update_user_payable_item,
    
)

from .models import (
    HostBookingRequest,
    HostBookingRequestAddOn,
    HostBookingPaymentStatus,
    BookingRequestStatus,
    HostingClientType,
    AddOnPricingType,
    HostingClientProfile,

)


from django.contrib.auth.models import User

from expat_life.utils import mark_user_payable_item_paid


def money(value):
    """
    Convert value to Decimal money format.
    """
    return Decimal(str(value or 0)).quantize(
        Decimal('0.01'),
        rounding=ROUND_HALF_UP
    )


def get_hosting_commission_rate():
    """
    Get hosting commission percentage from PlatformFeeSettings.
    Default fallback: 10%.
    """
    settings_obj = get_platform_fee_settings()

    if (
        settings_obj and
        hasattr(settings_obj, 'hosting_partner_commission_percentage') and
        settings_obj.hosting_partner_commission_percentage is not None
    ):
        return money(settings_obj.hosting_partner_commission_percentage)

    return Decimal('10.00')


def get_unpaid_host_booking_for_user(user):
    """
    Return the active unpaid/due booking order for this user, if available.

    Rule:
    One active unpaid hosting booking order per user at a time.
    Paid/completed/cancelled bookings remain as history.
    """
    if not user or not user.is_authenticated:
        return None

    return HostBookingRequest.objects.filter(
        booked_by_user=user,
        payment_status__in=[
            HostBookingPaymentStatus.UNPAID,
            HostBookingPaymentStatus.DUE,
        ],
    ).exclude(
        status__in=[
            BookingRequestStatus.PAID,
            BookingRequestStatus.COMPLETED,
            BookingRequestStatus.CANCELLED,
            BookingRequestStatus.REJECTED,
        ]
    ).order_by('-submitted_at').first()

def calculate_host_booking_totals(
    *,
    selected_package=None,
    selected_meal_plan=None,
    selected_add_ons=None,
    number_of_guests=1,
):
    """
    Calculate package amount, meal amount, add-on amount, total,
    commission, and host net amount.

    selected_add_ons can be:
    - list/queryset of HostAddOnService objects
    - list of dicts like {'service': add_on, 'quantity': 2}
    """

    number_of_guests = int(number_of_guests or 1)
    if number_of_guests < 1:
        number_of_guests = 1

    package_amount = Decimal('0.00')
    meal_amount = Decimal('0.00')
    add_on_amount = Decimal('0.00')

    # Package price: fixed
    if selected_package:
        package_amount = money(getattr(selected_package, 'price_usd', 0))

    # Meal plan price: price per person × max(number_of_guests, minimum_people)
    if selected_meal_plan:
        meal_price_per_person = money(
            getattr(selected_meal_plan, 'price_per_person_usd', 0)
        )

        minimum_people = int(getattr(selected_meal_plan, 'minimum_people', 1) or 1)
        billable_people = max(number_of_guests, minimum_people)

        meal_amount = money(meal_price_per_person * billable_people)

    # Add-ons
    add_on_lines = []

    if selected_add_ons:
        for item in selected_add_ons:
            if isinstance(item, dict):
                add_on = item.get('service') or item.get('add_on') or item.get('add_on_service')
                quantity = int(item.get('quantity') or 1)
            else:
                add_on = item
                quantity = 1

            if not add_on:
                continue

            pricing_type = getattr(add_on, 'pricing_type', AddOnPricingType.FIXED)
            unit_price = money(getattr(add_on, 'price_usd', 0))

            if pricing_type == AddOnPricingType.PER_PERSON:
                quantity = number_of_guests
            elif quantity < 1:
                quantity = 1

            subtotal = money(unit_price * quantity)
            add_on_amount += subtotal

            add_on_lines.append({
                'add_on': add_on,
                'quantity': quantity,
                'unit_price': unit_price,
                'subtotal': subtotal,
                'pricing_type': pricing_type,
            })

    total_amount = money(package_amount + meal_amount + add_on_amount)

    commission_rate = get_hosting_commission_rate()

    commission_amount = money(
        total_amount * commission_rate / Decimal('100')
    )

    host_net_amount = money(total_amount - commission_amount)

    return {
        'package_amount': package_amount,
        'meal_amount': meal_amount,
        'add_on_amount': add_on_amount,
        'total_amount': total_amount,
        'commission_rate': commission_rate,
        'commission_amount': commission_amount,
        'host_net_amount': host_net_amount,
        'add_on_lines': add_on_lines,
    }

@transaction.atomic
def create_or_update_host_booking_order(
    *,
    user,
    venue,
    guest_profile=None,
    hosting_client_profile=None,
    client_type=HostingClientType.FOREIGNER,
    selected_package=None,
    selected_meal_plan=None,
    selected_add_ons=None,
    event_type=None,
    preferred_event_date=None,
    preferred_start_time=None,
    expected_duration_hours=None,
    number_of_guests=2,
    occasion_title='',
    occasion_note='',
    special_requests='',
):
    """
    Create or update one active unpaid hosting booking order for the user.

    Flow:
    - If user already has an unpaid/due booking, update that booking.
    - If no unpaid booking exists, create a new booking.
    - Recalculate package, meal, add-on, total, commission, and host net.
    - Recreate selected add-on line items.
    - Create/update matching UserPayableItem.
    - Link payable item to booking.

    For Guest app:
        client_type='foreigner'
        guest_profile=request.user.foreigner_profile

    For direct local client later:
        client_type='hosting_client'
        hosting_client_profile=request.user.hosting_client_profile
    """

    if not user or not user.is_authenticated:
        raise ValueError("Authenticated user is required.")

    if not venue:
        raise ValueError("Venue is required.")

    if not event_type:
        raise ValueError("Event type is required.")

    if not preferred_event_date:
        raise ValueError("Preferred event date is required.")

    number_of_guests = int(number_of_guests or 1)
    if number_of_guests < 1:
        number_of_guests = 1

    totals = calculate_host_booking_totals(
        selected_package=selected_package,
        selected_meal_plan=selected_meal_plan,
        selected_add_ons=selected_add_ons,
        number_of_guests=number_of_guests,
    )

    booking = get_unpaid_host_booking_for_user(user)

    if booking is None:
        booking = HostBookingRequest(
            booked_by_user=user,
            payment_status=HostBookingPaymentStatus.DUE,
            status=BookingRequestStatus.PAYMENT_DUE,
        )

    # ------------------------------------------------------------
    # Client details
    # ------------------------------------------------------------
    booking.guest_profile = guest_profile
    booking.hosting_client_profile = hosting_client_profile
    booking.booked_by_user = user
    booking.client_type = client_type

    # ------------------------------------------------------------
    # Venue / selections
    # ------------------------------------------------------------
    booking.venue = venue
    booking.host_profile = getattr(venue, 'host_profile', None)

    booking.selected_package = selected_package
    booking.selected_meal_plan = selected_meal_plan

    # ------------------------------------------------------------
    # Event details
    # ------------------------------------------------------------
    booking.event_type = event_type
    booking.preferred_event_date = preferred_event_date
    booking.preferred_start_time = preferred_start_time
    booking.expected_duration_hours = expected_duration_hours
    booking.number_of_guests = number_of_guests

    booking.occasion_title = occasion_title or ''
    booking.occasion_note = occasion_note or ''
    booking.special_requests = special_requests or ''

    # ------------------------------------------------------------
    # Snapshots
    # ------------------------------------------------------------
    booking.venue_name_snapshot = getattr(venue, 'venue_name', '') or ''

    if getattr(venue, 'host_profile', None):
        booking.host_business_name_snapshot = venue.host_profile.business_name or ''

    booking.package_title_snapshot = (
        selected_package.title if selected_package else ''
    )

    booking.meal_plan_title_snapshot = (
        selected_meal_plan.title if selected_meal_plan else ''
    )

    # ------------------------------------------------------------
    # Amount snapshots
    # ------------------------------------------------------------
    booking.package_amount_snapshot_usd = totals['package_amount']
    booking.meal_amount_snapshot_usd = totals['meal_amount']
    booking.add_on_amount_snapshot_usd = totals['add_on_amount']

    booking.total_amount_usd = totals['total_amount']
    booking.commission_rate_percent = totals['commission_rate']
    booking.ensolanka_commission_usd = totals['commission_amount']
    booking.host_net_amount_usd = totals['host_net_amount']

    booking.payment_status = HostBookingPaymentStatus.DUE
    booking.status = BookingRequestStatus.PAYMENT_DUE

    booking.save()

    # ------------------------------------------------------------
    # Recreate add-on line items
    # ------------------------------------------------------------
    booking.selected_add_ons.all().delete()

    for line in totals['add_on_lines']:
        add_on = line['add_on']

        HostBookingRequestAddOn.objects.create(
            booking_request=booking,
            add_on_service=add_on,
            add_on_title_snapshot=getattr(add_on, 'title', '') or '',
            pricing_type_snapshot=getattr(add_on, 'pricing_type', '') or '',
            quantity=line['quantity'],
            unit_price_snapshot_usd=line['unit_price'],
            subtotal_snapshot_usd=line['subtotal'],
        )

    # ------------------------------------------------------------
    # Create/update payable item
    # ------------------------------------------------------------
    reference_code = f"HOST_BOOKING_{booking.id}"

    booking.payment_reference_code = reference_code
    booking.save(update_fields=['payment_reference_code', 'updated_at'])

    payable_profile_type = client_type

    # Safety fallback:
    # if guest_profile exists, this is a Foreigner payable item.
    if guest_profile:
        payable_profile_type = 'foreigner'

    # if direct hosting client exists, this is a Hosting Client payable item.
    if hosting_client_profile:
        payable_profile_type = 'hosting_client'

    payable_title = f"Host Event Booking - {booking.venue_name_snapshot}"

    payable_description = (
        f"Booking for {booking.get_event_type_display()} at "
        f"{booking.venue_name_snapshot}. "
        f"Includes selected event package, meal plan, and add-on services where applicable."
    )

    payable_item = create_or_update_user_payable_item(
        user=user,
        profile_type=payable_profile_type,
        payment_for='host_event_booking',
        title=payable_title,
        amount_usd=booking.total_amount_usd,
        amount_state='confirmed',
        payment_status='due',
        description=payable_description,
        is_optional=False,
        reference_code=reference_code,
        notes=f"Hosting booking order #{booking.id}",
    )

    booking.payable_item = payable_item
    booking.save(update_fields=['payable_item', 'updated_at'])

    return booking

def create_or_update_guest_host_booking_order(
    *,
    user,
    venue,
    selected_package=None,
    selected_meal_plan=None,
    selected_add_ons=None,
    event_type=None,
    preferred_event_date=None,
    preferred_start_time=None,
    expected_duration_hours=None,
    number_of_guests=2,
    occasion_title='',
    occasion_note='',
    special_requests='',
):
    """
    Wrapper for Live in Sri Lanka Guest / Foreigner bookings.
    """

    guest_profile = getattr(user, 'foreigner_profile', None)

    if not guest_profile:
        raise ValueError("User does not have a Foreigner / Guest profile.")

    return create_or_update_host_booking_order(
        user=user,
        venue=venue,
        guest_profile=guest_profile,
        hosting_client_profile=None,
        client_type=HostingClientType.FOREIGNER,
        selected_package=selected_package,
        selected_meal_plan=selected_meal_plan,
        selected_add_ons=selected_add_ons,
        event_type=event_type,
        preferred_event_date=preferred_event_date,
        preferred_start_time=preferred_start_time,
        expected_duration_hours=expected_duration_hours,
        number_of_guests=number_of_guests,
        occasion_title=occasion_title,
        occasion_note=occasion_note,
        special_requests=special_requests,
    )

def create_or_update_local_client_host_booking_order(
    *,
    user,
    venue,
    selected_package=None,
    selected_meal_plan=None,
    selected_add_ons=None,
    event_type=None,
    preferred_event_date=None,
    preferred_start_time=None,
    expected_duration_hours=None,
    number_of_guests=2,
    occasion_title='',
    occasion_note='',
    special_requests='',
):
    """
    Wrapper for direct Hosting Client bookings through hosting_app.
    """

    hosting_client_profile = getattr(user, 'hosting_client_profile', None)

    if not hosting_client_profile:
        raise ValueError("User does not have a Hosting Client profile.")

    return create_or_update_host_booking_order(
        user=user,
        venue=venue,
        guest_profile=None,
        hosting_client_profile=hosting_client_profile,
        client_type=HostingClientType.HOSTING_CLIENT,
        selected_package=selected_package,
        selected_meal_plan=selected_meal_plan,
        selected_add_ons=selected_add_ons,
        event_type=event_type,
        preferred_event_date=preferred_event_date,
        preferred_start_time=preferred_start_time,
        expected_duration_hours=expected_duration_hours,
        number_of_guests=number_of_guests,
        occasion_title=occasion_title,
        occasion_note=occasion_note,
        special_requests=special_requests,
    )

@transaction.atomic
def mark_host_booking_paid_from_payable_item(payable_item, notes=''):
    """
    Mark hosting booking as paid after the related UserPayableItem is paid.

    This should be called inside payment success handling when:
        payable_item.payment_for == 'host_event_booking'
    """

    if not payable_item:
        return None

    if payable_item.payment_for != 'host_event_booking':
        return None

    booking = getattr(payable_item, 'host_booking_request', None)

    if not booking:
        return None

    booking.payment_status = HostBookingPaymentStatus.PAID
    booking.status = BookingRequestStatus.PAID
    booking.paid_at = timezone.now()

    if not booking.guest_confirmed_at:
        booking.guest_confirmed_at = timezone.now()

    if notes:
        existing_note = booking.admin_review_note or ''
        booking.admin_review_note = (
            existing_note + f"\n\nPayment note: {notes}"
        ).strip()

    booking.save(update_fields=[
        'payment_status',
        'status',
        'paid_at',
        'guest_confirmed_at',
        'admin_review_note',
        'updated_at',
    ])

    return booking



#=============================================================

def detect_user_hosting_client_type(user):
    """
    Detect the best client_type for an existing logged-in user.
    Keep values aligned with UserPayableItem profile_type choices.
    """

    if not user or not user.is_authenticated:
        return HostingClientType.HOSTING_CLIENT

    if hasattr(user, 'foreigner_profile'):
        return HostingClientType.FOREIGNER

    if hasattr(user, 'dating_partner_profile'):
        return HostingClientType.DATING_PARTNER

    if hasattr(user, 'rental_owner_profile'):
        return HostingClientType.RENTAL_OWNER

    if hasattr(user, 'service_partner_profile'):
        return HostingClientType.SERVICE_PARTNER

    if hasattr(user, 'property_manager_client_profile'):
        return HostingClientType.PROPERTY_CARE_OWNER

    if hasattr(user, 'affiliate_profile'):
        return HostingClientType.AFFILIATE

    return HostingClientType.HOSTING_CLIENT

def get_existing_profile_basic_details(user):
    """
    Collect basic details from existing platform profiles if available.
    This avoids forcing existing users to re-enter details.
    """

    full_name = user.get_full_name() or user.username
    email = user.email or ''
    phone_number = ''
    whatsapp_number = ''
    city = ''
    country = 'Sri Lanka'
    source_profile_type = ''
    source_profile_id = None

    profile_map = [
        ('foreigner', getattr(user, 'foreigner_profile', None)),
        ('dating_partner', getattr(user, 'dating_partner_profile', None)),
        ('rental_owner', getattr(user, 'rental_owner_profile', None)),
        ('service_partner', getattr(user, 'service_partner_profile', None)),
        ('property_care_owner', getattr(user, 'property_manager_client_profile', None)),
        ('affiliate', getattr(user, 'affiliate_profile', None)),
    ]

    for profile_type, profile in profile_map:
        if profile:
            source_profile_type = profile_type
            source_profile_id = profile.id

            full_name = (
                getattr(profile, 'full_name', '') or
                getattr(profile, 'name', '') or
                getattr(profile, 'contact_person_name', '') or
                full_name
            )

            email = (
                getattr(profile, 'email', '') or
                email
            )

            phone_number = (
                getattr(profile, 'phone_number', '') or
                getattr(profile, 'phone', '') or
                phone_number
            )

            whatsapp_number = (
                getattr(profile, 'whatsapp_number', '') or
                getattr(profile, 'whatsapp', '') or
                whatsapp_number
            )

            city = getattr(profile, 'city', '') or city
            country = getattr(profile, 'country', '') or country

            break

    return {
        'full_name': full_name,
        'email': email,
        'phone_number': phone_number,
        'whatsapp_number': whatsapp_number,
        'city': city,
        'country': country,
        'source_profile_type': source_profile_type,
        'source_profile_id': source_profile_id,
    }

def get_or_create_hosting_client_profile_for_user(user):
    """
    Create HostingClientProfile for existing logged-in users if missing.
    """

    if not user or not user.is_authenticated:
        return None

    existing = getattr(user, 'hosting_client_profile', None)
    if existing:
        return existing

    client_type = detect_user_hosting_client_type(user)

    details = get_existing_profile_basic_details(user)

    hosting_client = HostingClientProfile.objects.create(
        user=user,
        client_type=client_type,
        full_name=details['full_name'],
        email=details['email'],
        phone_number=details['phone_number'],
        whatsapp_number=details['whatsapp_number'],
        country=details['country'],
        city=details['city'],
        created_from_existing_profile=True if details['source_profile_type'] else False,
        source_profile_type=details['source_profile_type'],
        source_profile_id=details['source_profile_id'],
    )

    return hosting_client

@transaction.atomic
def mark_host_booking_paid_from_payable_item(
    payable_item,
    *,
    notes='',
    credit_host_wallet=False,
):
    """
    Mark related HostBookingRequest as paid after its UserPayableItem is paid.

    Use credit_host_wallet=False by default if you want to credit the hosting partner
    only after event delivery/completion.

    Use credit_host_wallet=True only if the platform policy is to credit host wallet
    immediately after guest/local-client payment.
    """

    if not payable_item:
        return None

    if payable_item.payment_for != 'host_event_booking':
        return None

    booking = getattr(payable_item, 'host_booking_request', None)

    if not booking:
        booking = HostBookingRequest.objects.filter(
            payable_item=payable_item
        ).first()

    if not booking:
        return None

    booking.payment_status = HostBookingPaymentStatus.PAID
    booking.status = BookingRequestStatus.PAID
    booking.paid_at = timezone.now()

    if not booking.guest_confirmed_at:
        booking.guest_confirmed_at = timezone.now()

    if notes:
        existing_note = booking.admin_review_note or ''
        booking.admin_review_note = (
            existing_note + f"\n\nPayment note: {notes}"
        ).strip()

    booking.save(update_fields=[
        'payment_status',
        'status',
        'paid_at',
        'guest_confirmed_at',
        'admin_review_note',
        'updated_at',
    ])

    if credit_host_wallet:
        from expat_life.utils import credit_hosting_partner_earning_from_event_booking_payment

        credit_hosting_partner_earning_from_event_booking_payment(
            booking=booking,
            paid_item=payable_item,
            notes=notes or 'Host event booking paid.'
        )

    return booking

def mark_hosting_client_transaction_items_paid(
    payment_transaction,
    notes='',
    credit_host_wallet=False,
):
    """
    Marks all payable items inside a successful Hosting Client PaymentTransaction as paid.

    Main supported payable item:
    - host_event_booking

    Also marks the related HostBookingRequest as paid.

    By default, credit_host_wallet=False because it is safer to credit the
    hosting partner wallet after event delivery/completion.
    """

    if not payment_transaction:
        return {
            "success": False,
            "processed": [],
            "failed": ["Payment transaction not found."],
        }

    if payment_transaction.profile_type != 'hosting_client':
        return {
            "success": False,
            "processed": [],
            "failed": ["Payment transaction is not for a Hosting Client profile."],
        }

    payment_order = {
        'host_event_booking': 1,
    }

    transaction_items = list(
        payment_transaction.items.select_related('payable_item').all()
    )

    transaction_items.sort(
        key=lambda transaction_item: payment_order.get(
            transaction_item.payable_item.payment_for,
            999
        )
    )

    processed = []
    failed = []

    for transaction_item in transaction_items:
        payable_item = transaction_item.payable_item
        payment_for = getattr(payable_item, 'payment_for', None)

        if not payment_for:
            failed.append(
                f"Payable item #{payable_item.id} has no payment_for value."
            )
            continue

        item_notes = notes or f"Paid through transaction #{payment_transaction.id}"

        if payment_for == 'host_event_booking':
            paid_item = mark_user_payable_item_paid(
                user=payment_transaction.user,
                profile_type='hosting_client',
                payment_for='host_event_booking',
                notes=item_notes,
                payable_item=payable_item,
            )

            if not paid_item:
                failed.append(
                    f"Could not mark hosting client payable item #{payable_item.id} as paid."
                )
                continue

            booking = mark_host_booking_paid_from_payable_item(
                paid_item,
                notes=item_notes,
                credit_host_wallet=credit_host_wallet,
            )

            if not booking:
                failed.append(
                    f"Payable item #{payable_item.id} was paid, but the related booking could not be marked as paid."
                )
                continue

            processed.append(payment_for)
            continue

        paid_item = mark_user_payable_item_paid(
            user=payment_transaction.user,
            profile_type='hosting_client',
            payment_for=payment_for,
            notes=item_notes,
            payable_item=payable_item,
        )

        payable_item.refresh_from_db()

        if paid_item and payable_item.payment_status == 'paid':
            processed.append(payment_for)
        else:
            failed.append(
                f"Could not mark payable item #{payable_item.id} ({payment_for}) as paid."
            )

    return {
        "success": len(failed) == 0,
        "processed": processed,
        "failed": failed,
    }

