from django.core.mail import send_mail
from django.conf import settings
from .models import PlatformFeeSettings
from decimal import Decimal, ROUND_HALF_UP
from django.utils import timezone
from datetime import timedelta
from .models import UserPayableItem, WalletAccount, WalletTransaction, RefundableDeposit, PaymentTransaction
from django.db import transaction
from django.core.exceptions import ValidationError, ObjectDoesNotExist
import logging
import re
from property_manager_app.models import PropertyCarePlan
from hosting_app.models import HostProfile
from dateutil.relativedelta import relativedelta

logger = logging.getLogger(__name__)


def send_account_credentials_email(full_name, email, username, password, portal_name='Sri Lanka Expat Life'):
    subject = f'Welcome to {portal_name} - Your Login Details'
    message = f"""
Hello {full_name},

Welcome to {portal_name}.

Your account has been created successfully.

Username: {username}
Password: {password}

Please keep these details safe.

Regards,
{portal_name} Team
""".strip()

    send_mail(
        subject,
        message,
        settings.DEFAULT_FROM_EMAIL,
        [email],
        fail_silently=False,
    )

FINAL_PAYMENT_STATUSES = ['paid', 'waived', 'cancelled', 'expired']


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 create_or_update_user_payable_item(
    *,
    user,
    profile_type,
    payment_for,
    title,
    amount_usd=Decimal('0.00'),
    amount_state='estimated',
    payment_status='upcoming',
    description='',
    is_optional=False,
    reference_code='',
    due_date=None,
    effective_from=None,
    notes='',
):
    """
    Generic payable item create/update helper.

    If reference_code is supplied, it becomes part of the lookup identity.
    This is necessary for payable items that may exist multiple times
    for the same user/profile/payment type, such as:
    - Property care monthly fee per finalized plan
    - Future multi-property or multi-order items
    """

    lookup = {
        'user': user,
        'profile_type': profile_type,
        'payment_for': payment_for,
        'is_active': True,
    }

    # Important for plan/property-specific payable items
    if reference_code:
        lookup['reference_code'] = reference_code

    payable_item, created = UserPayableItem.objects.get_or_create(
        **lookup,
        defaults={
            'title': title,
            'description': description,
            'amount_state': amount_state,
            'amount_usd': amount_usd,
            'payment_status': payment_status,
            'is_optional': is_optional,
            'reference_code': reference_code,
            'due_date': due_date,
            'effective_from': effective_from,
            'notes': notes,
        }
    )

    if created:
        return payable_item

    # Always allow display / metadata updates
    payable_item.title = title
    payable_item.description = description
    payable_item.is_optional = is_optional
    payable_item.reference_code = reference_code or payable_item.reference_code
    payable_item.due_date = due_date
    payable_item.effective_from = effective_from
    payable_item.notes = notes

    # Do not overwrite finalized payment statuses
    if payable_item.payment_status not in FINAL_PAYMENT_STATUSES:
        payable_item.amount_state = amount_state
        payable_item.amount_usd = amount_usd
        payable_item.payment_status = payment_status

    payable_item.save()
    return payable_item


def update_foreigner_package_based_payable_items(user, lead):
    """
    Update foreigner payable items that depend on the currently selected lifestyle package.

    Business rules:
    - If account confirmation is paid, it remains paid and should not be overwritten.
    - If account confirmation is paid and one-time service fee is not paid,
      one-time service fee becomes due only if the amount is greater than 0.
    - If account confirmation is not paid, one-time service fee stays upcoming.
    - If one-time service fee is paid, it remains paid and should not be overwritten.
    """

    selected_package = getattr(lead, 'interested_package', None)

    confirmation_fee = Decimal('0.00')
    one_time_service_fee = Decimal('0.00')

    if selected_package:
        confirmation_fee = Decimal(str(selected_package.confirmation_fee_usd or 0))
        one_time_service_fee = Decimal(str(selected_package.service_fee_amount or 0))

    confirmation_item = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for='account_confirmation',
        is_active=True
    ).first()

    service_item = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for='one_time_service_fee',
        is_active=True
    ).first()

    confirmation_paid = (
        confirmation_item is not None and confirmation_item.payment_status == 'paid'
    )

    service_fee_paid = (
        service_item is not None and service_item.payment_status == 'paid'
    )

    # ----------------------------
    # Decide confirmation fee status
    # ----------------------------
    confirmation_payment_status = 'upcoming'

    if not selected_package:
        confirmation_payment_status = 'upcoming'

    elif confirmation_paid:
        confirmation_payment_status = 'paid'

    elif confirmation_fee > 0:
        confirmation_payment_status = 'due'

    else:
        confirmation_payment_status = 'upcoming'

    # ----------------------------
    # Decide one-time service fee status
    # ----------------------------
    service_payment_status = 'upcoming'

    if not selected_package:
        service_payment_status = 'upcoming'

    elif service_fee_paid:
        service_payment_status = 'paid'

    elif confirmation_paid and one_time_service_fee > 0:
        # Once confirmation is paid, service fee becomes due,
        # but only if the selected package has a service fee amount.
        service_payment_status = 'due'

    elif service_item and service_item.payment_status == 'due' and one_time_service_fee > 0:
        service_payment_status = 'due'

    else:
        service_payment_status = 'upcoming'

    # ----------------------------
    # Create / update confirmation fee
    # ----------------------------
    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='account_confirmation',
        title='Foreigner Account Confirmation Fee',
        amount_usd=confirmation_fee,
        amount_state='confirmed' if selected_package else 'not_decided',
        payment_status=confirmation_payment_status,
        description='Confirmation fee for serious client verification and opening service access and processing steps.',
        is_optional=False,
    )

    # ----------------------------
    # Create / update one-time service fee
    # ----------------------------
    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='one_time_service_fee',
        title='Foreigner One-Time Service Fee',
        amount_usd=one_time_service_fee,
        amount_state='confirmed' if selected_package else 'not_decided',
        payment_status=service_payment_status,
        description='One-time facilitation and support fee based on the selected package.',
        is_optional=False,
    )

def update_foreigner_furniture_payable_items(user, lead):
    """
    Update foreigner payable items that depend on the selected furniture package.

    Business rules:
    - If furniture deposit is paid, it remains paid and should not be overwritten.
    - If furniture deposit is paid and furniture monthly rental is not paid,
      furniture monthly rental becomes due.
    - If furniture deposit is not paid, furniture monthly rental stays upcoming.
    - If furniture monthly rental is paid but deposit is not paid,
      furniture deposit should become due.
    """

    furniture_package = getattr(lead, 'selected_furniture_package', None)

    monthly_rental = Decimal('0.00')
    furniture_deposit = Decimal('0.00')

    if furniture_package:
        monthly_rental = Decimal(str(furniture_package.monthly_rental_usd or 0))
        furniture_deposit = furniture_package.calculated_deposit_usd

    deposit_item = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental_deposit',
        is_active=True
    ).first()

    monthly_item = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental',
        is_active=True
    ).first()

    furniture_deposit_paid = (
        deposit_item is not None and deposit_item.payment_status == 'paid'
    )

    furniture_monthly_paid = (
        monthly_item is not None and monthly_item.payment_status == 'paid'
    )

    # ----------------------------
    # Decide furniture deposit status
    # ----------------------------
    deposit_payment_status = 'upcoming'

    if not furniture_package:
        deposit_payment_status = 'upcoming'

    elif furniture_deposit_paid:
        deposit_payment_status = 'paid'

    elif furniture_monthly_paid:
        deposit_payment_status = 'due'

    elif deposit_item and deposit_item.payment_status == 'due':
        deposit_payment_status = 'due'

    else:
        deposit_payment_status = 'upcoming'

    # ----------------------------
    # Decide furniture monthly rental status
    # ----------------------------
    monthly_payment_status = 'upcoming'

    if not furniture_package:
        monthly_payment_status = 'upcoming'

    elif furniture_monthly_paid:
        monthly_payment_status = 'paid'

    elif furniture_deposit_paid:
        monthly_payment_status = 'due'

    else:
        monthly_payment_status = 'upcoming'

    # ----------------------------
    # Create / update furniture deposit
    # ----------------------------
    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental_deposit',
        title='Furniture Rental Deposit',
        amount_usd=furniture_deposit,
        amount_state='confirmed' if furniture_package else 'not_decided',
        payment_status=deposit_payment_status,
        description=(
            'Furniture rental deposit based on the selected package. '
            'Usually calculated using the package deposit month setting.'
        ),
        is_optional=True,
    )

    # ----------------------------
    # Create / update furniture monthly rental
    # ----------------------------
    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental',
        title='Furniture Monthly Rental',
        amount_usd=monthly_rental,
        amount_state='confirmed' if furniture_package else 'not_decided',
        payment_status=monthly_payment_status,
        description='Monthly furniture rental amount based on the selected furniture package.',
        is_optional=True,
    )

def update_foreigner_rental_payable_items(user, lead):
    """
    Update foreigner payable items that depend on the selected final rental property.

    Business rules:
    - If rental deposit is paid, it remains paid and should not be overwritten.
    - If rental deposit is paid and monthly rental is not paid, monthly rental becomes due.
    - If rental deposit is not paid, monthly rental stays upcoming.
    - If monthly rental is paid but deposit is not paid, deposit should become due.
    """

    final_listing = getattr(lead, 'final_rental_property', None)

    monthly_rental = Decimal('0.00')
    rental_deposit = Decimal('0.00')

    if final_listing:
        monthly_rental = Decimal(str(final_listing.monthly_rent_usd or 0))
        rental_deposit = Decimal(str(final_listing.security_deposit_usd or 0))

    deposit_item = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for='rental_deposit',
        is_active=True
    ).first()

    monthly_item = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for='monthly_rental',
        is_active=True
    ).first()

    rental_deposit_paid = (
        deposit_item is not None and deposit_item.payment_status == 'paid'
    )

    monthly_rental_paid = (
        monthly_item is not None and monthly_item.payment_status == 'paid'
    )

    # ----------------------------
    # Decide deposit payment status
    # ----------------------------
    deposit_payment_status = 'upcoming'

    if not final_listing:
        deposit_payment_status = 'upcoming'

    elif rental_deposit_paid:
        deposit_payment_status = 'paid'

    elif monthly_rental_paid:
        # Safety case:
        # If monthly rental is somehow already paid,
        # deposit should not stay upcoming.
        deposit_payment_status = 'due'

    elif deposit_item and deposit_item.payment_status == 'due':
        # Keep it due if it was already due.
        deposit_payment_status = 'due'

    else:
        deposit_payment_status = 'upcoming'

    # ----------------------------
    # Decide monthly rental status
    # ----------------------------
    monthly_payment_status = 'upcoming'

    if not final_listing:
        monthly_payment_status = 'upcoming'

    elif monthly_rental_paid:
        monthly_payment_status = 'paid'

    elif rental_deposit_paid:
        # Once deposit is paid, first monthly rental should be due.
        monthly_payment_status = 'due'

    else:
        monthly_payment_status = 'upcoming'

    # ----------------------------
    # Create / update rental deposit
    # ----------------------------
    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='rental_deposit',
        title='Rental Deposit',
        amount_usd=rental_deposit,
        amount_state='confirmed' if final_listing else 'not_decided',
        payment_status=deposit_payment_status,
        description='Rental deposit based on the selected final rental property.',
        is_optional=False,
    )

    # ----------------------------
    # Create / update monthly rental
    # ----------------------------
    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='monthly_rental',
        title='Monthly Rental',
        amount_usd=monthly_rental,
        amount_state='confirmed' if final_listing else 'not_decided',
        payment_status=monthly_payment_status,
        description='Monthly rental amount based on the selected final rental property.',
        is_optional=False,
    )

def create_foreigner_default_payable_items(user, lead):
    settings_obj = get_platform_fee_settings()
    selected_package = lead.interested_package

    featured_fee = Decimal(str(settings_obj.foreigner_featured_profile_fee_usd)) if settings_obj else Decimal('10.00')

    confirmation_fee = Decimal('0.00')
    one_time_service_fee = Decimal('0.00')

    if selected_package:
        confirmation_fee = Decimal(str(selected_package.confirmation_fee_usd or 0))
        one_time_service_fee = Decimal(str(selected_package.service_fee_amount or 0))

    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='featured_profile_fee',
        title='Featured / Premium Foreigner Profile Fee',
        amount_usd=featured_fee,
        amount_state='confirmed',
        payment_status='due',
        description='Optional premium visibility fee for better profile exposure and additional attention.',
        is_optional=True,
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='account_confirmation',
        title='Foreigner Account Confirmation Fee',
        amount_usd=confirmation_fee,
        amount_state='confirmed' if selected_package else 'not_decided',
        payment_status='due' if confirmation_fee > 0 else 'upcoming',
        description='Confirmation fee for serious client verification and opening service access and processing steps.',
        is_optional=False,
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='one_time_service_fee',
        title='Foreigner One-Time Service Fee',
        amount_usd=one_time_service_fee,
        amount_state='confirmed' if selected_package else 'not_decided',
        payment_status='upcoming',
        description='One-time facilitation and support fee based on the selected package.',
        is_optional=False,
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='rental_deposit',
        title='Rental Deposit',
        amount_usd=Decimal('0.00'),
        amount_state='not_decided',
        payment_status='upcoming',
        description='Will be updated after a rental property is selected and confirmed.',
        is_optional=False,
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='monthly_rental',
        title='Monthly Rental',
        amount_usd=Decimal('0.00'),
        amount_state='not_decided',
        payment_status='upcoming',
        description='Will be updated after the selected rental property is finalized.',
        is_optional=False,
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental',
        title='Furniture Monthly Rental',
        amount_usd=Decimal('0.00'),
        amount_state='not_decided',
        payment_status='upcoming',
        description='Will be updated after the furniture package is selected and finalized.',
        is_optional=True,
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental_deposit',
        title='Furniture Rental Deposit',
        amount_usd=Decimal('0.00'),
        amount_state='not_decided',
        payment_status='upcoming',
        description='Will be updated after the selected furniture package is finalized. Usually calculated as 3 months of furniture rental.',
        is_optional=True,
    )

def update_dating_partner_payable_items(user):
    settings_obj = get_platform_fee_settings()

    confirmation_fee = Decimal(str(settings_obj.dating_partner_confirmation_fee_usd)) if settings_obj else Decimal('50.00')
    featured_fee = Decimal(str(settings_obj.dating_partner_featured_profile_fee_usd)) if settings_obj else Decimal('10.00')

    create_or_update_user_payable_item(
        user=user,
        profile_type='dating_partner',
        payment_for='dating_confirmation',
        title='Dating Partner Confirmation Fee',
        amount_usd=confirmation_fee,
        amount_state='confirmed',
        payment_status='due',
        description='Confirmation fee for dating partner profile review, activation, and processing.',
        is_optional=False,
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='dating_partner',
        payment_for='dating_partner_featured',
        title='Dating Partner Featured / Premium Profile Fee',
        amount_usd=featured_fee,
        amount_state='confirmed',
        payment_status='due',
        description='Optional premium visibility fee for better profile exposure and more attention.',
        is_optional=True,
    )

def update_rental_owner_payable_items(user):
    settings_obj = get_platform_fee_settings()

    featured_fee = (
        Decimal(str(settings_obj.rental_owner_featured_profile_fee_usd))
        if settings_obj else Decimal('10.00')
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='rental_owner',
        payment_for='rental_owner_featured',
        title='Rental Owner Featured / Premium Profile Fee',
        amount_usd=featured_fee,
        amount_state='confirmed',
        payment_status='due',
        description='Optional premium visibility fee to feature the rental owner profile and highlight current and future listings.',
        is_optional=True,
    )
#================================================
#Service Partner (Assistants)
#=================================================

def update_service_partner_payable_items(user):
    settings_obj = get_platform_fee_settings()

    featured_fee = (
        Decimal(str(settings_obj.service_partner_featured_account_fee_usd))
        if settings_obj else Decimal('10.00')
    )

    create_or_update_user_payable_item(
        user=user,
        profile_type='service_partner',
        payment_for='service_partner_featured',
        title='Service Partner Featured / Premium Account Fee',
        amount_usd=featured_fee,
        amount_state='confirmed',
        payment_status='due',
        description='Optional premium visibility fee for service partners such as assistants, helpers, drivers, translators, and other local support providers.',
        is_optional=True,
    )

def mark_service_partner_featured_fee_paid(user, notes='', payable_item=None):
    """
    Mark service partner featured / premium visibility fee as paid
    and update service partner featured flag.
    """

    paid_item = mark_user_payable_item_paid(
        user=user,
        profile_type='service_partner',
        payment_for='service_partner_featured',
        notes=notes,
        payable_item=payable_item,
    )

    if not paid_item:
        return False

    service_partner = getattr(user, 'service_partner_profile', None)

    if service_partner:
        changed_fields = []

        if hasattr(service_partner, 'is_featured') and not service_partner.is_featured:
            service_partner.is_featured = True
            changed_fields.append('is_featured')

        if changed_fields:
            service_partner.save(update_fields=changed_fields)

    return True


def mark_service_partner_payable_item_paid(user, payment_for, notes='', payable_item=None):
    """
    Router for Service Partner payable item business logic.
    """

    if payment_for == 'service_partner_featured':
        return mark_service_partner_featured_fee_paid(
            user=user,
            notes=notes,
            payable_item=payable_item,
        )

    return False


def mark_service_partner_transaction_items_paid(payment_transaction, notes=''):
    """
    Marks all payable items inside a successful Service Partner PaymentTransaction as paid.
    """

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

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

    payment_order = {
        'service_partner_featured': 1,
    }

    special_payment_types = set(payment_order.keys())

    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 in special_payment_types:
            result = mark_service_partner_payable_item_paid(
                user=payment_transaction.user,
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )
        else:
            result = mark_user_payable_item_paid(
                user=payment_transaction.user,
                profile_type='service_partner',
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )

        payable_item.refresh_from_db()

        if result 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,
    }

def sync_foreigner_payable_items(user, lead):
    if not lead:
        return

    #create_foreigner_default_payable_items(user, lead)
    update_foreigner_package_based_payable_items(user, lead)
    update_foreigner_rental_payable_items(user, lead)
    update_foreigner_furniture_payable_items(user, lead)



#----------When payments are successful for each payable item we call these updates related to that payment:

def get_active_payable_item(user, profile_type, payment_for):
    return UserPayableItem.objects.filter(
        user=user,
        profile_type=profile_type,
        payment_for=payment_for,
        is_active=True
    ).first()


def mark_user_payable_item_paid(user, profile_type, payment_for, notes='', payable_item=None):
    if payable_item is None:
        payable_item = get_active_payable_item(user, profile_type, payment_for)

    if not payable_item:
        return None

    if payable_item.user != user:
        return None

    if payable_item.profile_type != profile_type:
        return None

    if payable_item.payment_for != payment_for:
        return None

    if not payable_item.is_active:
        return None

    if payable_item.payment_status != 'paid':
        now = timezone.now()
        payable_item.payment_status = 'paid'
        payable_item.paid_at = now

        if not payable_item.effective_from:
            payable_item.effective_from = now.date()

        if notes:
            payable_item.notes = notes

        payable_item.save(update_fields=[
            'payment_status',
            'paid_at',
            'effective_from',
            'notes',
            'updated_at',
        ])

    return payable_item



"""
def mark_user_payable_item_paid(user, profile_type, payment_for, notes=''):
    
    print("MARK USER PAYABLE ITEM PAID CALLED")
    print("USER:", user)
    print("PROFILE TYPE:", profile_type)
    print("PAYMENT FOR:", payment_for)

    payable_item = get_active_payable_item(user, profile_type, payment_for)

    print("FOUND PAYABLE ITEM:", payable_item)

    if payable_item:
        print("ITEM ID:", payable_item.id)
        print("ITEM STATUS BEFORE:", payable_item.payment_status)
        print("ITEM ACTIVE:", payable_item.is_active)

    if not payable_item:
        return None

    ...
"""

# foreigner/guest confirmation fee paid

def mark_foreigner_confirmation_fee_paid(user, notes=''):
    foreigner = getattr(user, 'foreigner_profile', None)
    if not foreigner or not foreigner.lead:
        return False

    lead = foreigner.lead

    confirmation_item = mark_user_payable_item_paid(
        user=user,
        profile_type='foreigner',
        payment_for='account_confirmation',
        notes=notes
    )
    if not confirmation_item:
        return False

    # Update lead
    lead_changed_fields = []

    if not lead.is_confirmed_foreigner:
        lead.is_confirmed_foreigner = True
        lead_changed_fields.append('is_confirmed_foreigner')

    if lead.lead_status != 'contacted':
        lead.lead_status = 'contacted'
        lead_changed_fields.append('lead_status')

    if lead_changed_fields:
        lead.save(update_fields=lead_changed_fields)

    # Update foreigner profile
    foreigner_changed_fields = []

    if not foreigner.is_confirmed:
        foreigner.is_confirmed = True
        foreigner_changed_fields.append('is_confirmed')

    if not foreigner.can_view_private_listings:
        foreigner.can_view_private_listings = True
        foreigner_changed_fields.append('can_view_private_listings')

    if not foreigner.can_book_arrangements:
        foreigner.can_book_arrangements = True
        foreigner_changed_fields.append('can_book_arrangements')

    if foreigner_changed_fields:
        foreigner.save(update_fields=foreigner_changed_fields)

    # Move service fee to due
    service_item = get_active_payable_item(user, 'foreigner', 'one_time_service_fee')
    if service_item and service_item.payment_status != 'paid' and service_item.amount_usd > 0:
        service_item.payment_status = 'due'
        service_item.amount_state = 'confirmed'
        service_item.notes = 'Activated after account confirmation payment.'
        service_item.save(update_fields=['payment_status', 'amount_state', 'notes', 'updated_at'])

    return True

# Guest featuring fee

def mark_foreigner_featured_fee_paid(user, notes=''):
    foreigner = getattr(user, 'foreigner_profile', None)

    featured_item = mark_user_payable_item_paid(
        user=user,
        profile_type='foreigner',
        payment_for='featured_profile_fee',
        notes=notes
    )
    if not featured_item:
        return False

    if foreigner and hasattr(foreigner, 'is_featured') and not foreigner.is_featured:
        foreigner.is_featured = True
        foreigner.save(update_fields=['is_featured'])

    return True

# Guest one time service fee

def mark_foreigner_service_fee_paid(user, notes=''):
    foreigner = getattr(user, 'foreigner_profile', None)
    if not foreigner or not foreigner.lead:
        return False

    lead = foreigner.lead

    service_item = mark_user_payable_item_paid(
        user=user,
        profile_type='foreigner',
        payment_for='one_time_service_fee',
        notes=notes
    )
    if not service_item:
        return False

    # Lead becomes qualified
    if lead.lead_status != 'qualified':
        lead.lead_status = 'qualified'
        lead.save(update_fields=['lead_status'])

    # Lock main package once service fee is paid
    if not foreigner.is_main_package_locked:
        foreigner.is_main_package_locked = True
        foreigner.save(update_fields=['is_main_package_locked'])

    # Rental deposit becomes due if final property selected
    if getattr(lead, 'final_rental_property', None):
        rental_deposit_item = get_active_payable_item(user, 'foreigner', 'rental_deposit')
        if rental_deposit_item and rental_deposit_item.payment_status != 'paid' and rental_deposit_item.amount_usd > 0:
            rental_deposit_item.payment_status = 'due'
            rental_deposit_item.amount_state = 'confirmed'
            rental_deposit_item.notes = 'Activated after one-time service fee payment.'
            rental_deposit_item.save(update_fields=['payment_status', 'amount_state', 'notes', 'updated_at'])

    # Furniture deposit becomes due if furniture package selected
    if getattr(lead, 'selected_furniture_package', None):
        furniture_deposit_item = get_active_payable_item(user, 'foreigner', 'furniture_rental_deposit')
        if furniture_deposit_item and furniture_deposit_item.payment_status != 'paid' and furniture_deposit_item.amount_usd > 0:
            furniture_deposit_item.payment_status = 'due'
            furniture_deposit_item.amount_state = 'confirmed'
            furniture_deposit_item.notes = 'Activated after one-time service fee payment.'
            furniture_deposit_item.save(update_fields=['payment_status', 'amount_state', 'notes', 'updated_at'])

    return True

# Guest rental deposit
def mark_foreigner_rental_deposit_paid(user, notes=''):
    foreigner = getattr(user, 'foreigner_profile', None)
    if not foreigner:
        return False

    rental_deposit_item = mark_user_payable_item_paid(
        user=user,
        profile_type='foreigner',
        payment_for='rental_deposit',
        notes=notes
    )
    if not rental_deposit_item:
        return False

    # --- NEW: Create refundable deposit record ---
    deposit_amount = rental_deposit_item.amount_usd   # assuming the payable item stores the amount
    create_refundable_deposit(
        user=user,
        profile_type='foreigner',
        deposit_type='rental_deposit',
        amount_usd=deposit_amount,
        payable_item=rental_deposit_item,
        reference_code=rental_deposit_item.reference_code or '',
        notes=f"Deposit created from payment: {notes}",
        update_existing_if_duplicate=True   # <-- add this
    )
    # ---------------------------------------------

    # Mark rental cycle as active
    if not foreigner.is_rental_cycle_active:
        foreigner.is_rental_cycle_active = True
        foreigner.save(update_fields=['is_rental_cycle_active'])

    monthly_rental_item = get_active_payable_item(user, 'foreigner', 'monthly_rental')
    if monthly_rental_item and monthly_rental_item.payment_status != 'paid' and monthly_rental_item.amount_usd > 0:
        paid_date = rental_deposit_item.paid_at.date() if rental_deposit_item.paid_at else timezone.now().date()
        due_date = paid_date + timedelta(days=15)

        monthly_rental_item.payment_status = 'due'
        monthly_rental_item.amount_state = 'confirmed'
        monthly_rental_item.effective_from = paid_date
        monthly_rental_item.due_date = due_date
        monthly_rental_item.notes = 'Activated after rental deposit payment. Due 15 days after deposit payment.'
        monthly_rental_item.save(update_fields=[
            'payment_status',
            'amount_state',
            'effective_from',
            'due_date',
            'notes',
            'updated_at',
        ])

    return True

# Guest furniture rental deposit
def mark_foreigner_furniture_deposit_paid(user, notes=''):
    foreigner = getattr(user, 'foreigner_profile', None)
    if not foreigner:
        return False

    furniture_deposit_item = mark_user_payable_item_paid(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental_deposit',
        notes=notes
    )
    if not furniture_deposit_item:
        return False

    # --- NEW: Create refundable deposit record ---
    deposit_amount = furniture_deposit_item.amount_usd
    create_refundable_deposit(
        user=user,
        profile_type='foreigner',
        deposit_type='furniture_deposit',
        amount_usd=deposit_amount,
        payable_item=furniture_deposit_item,
        reference_code=furniture_deposit_item.reference_code or '',
        notes=f"Deposit created from payment: {notes}",
        update_existing_if_duplicate=True   # <-- add this
    )
    # ---------------------------------------------

    # Mark furniture cycle as active
    if not foreigner.is_furniture_cycle_active:
        foreigner.is_furniture_cycle_active = True
        foreigner.save(update_fields=['is_furniture_cycle_active'])

    furniture_monthly_item = get_active_payable_item(user, 'foreigner', 'furniture_rental')
    if furniture_monthly_item and furniture_monthly_item.payment_status != 'paid' and furniture_monthly_item.amount_usd > 0:
        paid_date = furniture_deposit_item.paid_at.date() if furniture_deposit_item.paid_at else timezone.now().date()
        due_date = paid_date + timedelta(days=25)

        furniture_monthly_item.payment_status = 'due'
        furniture_monthly_item.amount_state = 'confirmed'
        furniture_monthly_item.effective_from = paid_date
        furniture_monthly_item.due_date = due_date
        furniture_monthly_item.notes = 'Activated after furniture deposit payment. Due 25 days after deposit payment.'
        furniture_monthly_item.save(update_fields=[
            'payment_status',
            'amount_state',
            'effective_from',
            'due_date',
            'notes',
            'updated_at',
        ])

    return True


def mark_foreigner_host_event_booking_paid(user, notes='', payable_item=None):
    """
    Mark Guest host event booking payable item as paid,
    then update the linked HostBookingRequest as paid.
    """

    host_item = mark_user_payable_item_paid(
        user=user,
        profile_type='foreigner',
        payment_for='host_event_booking',
        notes=notes,
        payable_item=payable_item,
    )

    if not host_item:
        return False

    # Import here to avoid circular import
    from hosting_app.utils import mark_host_booking_paid_from_payable_item

    booking = mark_host_booking_paid_from_payable_item(
        payable_item=host_item,
        notes=notes,
    )

    if not booking:
        return False

    return True

def create_refundable_deposit(
    user,
    profile_type,
    deposit_type,
    amount_usd,
    payable_item=None,
    reference_code='',
    notes='',
    allow_duplicate_active=False,          # If True, creates even if active exists
    update_existing_if_duplicate=False,    # If True, updates existing active with new amount
):
    """
    Creates a refundable deposit record after a successful payment.

    Args:
        user: User instance
        profile_type: str (e.g., 'foreigner')
        deposit_type: str (e.g., 'rental_deposit')
        amount_usd: Decimal or float
        payable_item: Optional UserPayableItem instance (for linking)
        reference_code: str, external reference
        notes: str
        allow_duplicate_active: bool, if False raises when active deposit exists
        update_existing_if_duplicate: bool, if True updates existing active amount

    Returns:
        RefundableDeposit instance (new or existing)

    Raises:
        ValueError: If amount <= 0 or invalid parameters
        ValidationError: If duplicate active deposit and not allowed
    """
    # 1. Validate amount
    if isinstance(amount_usd, float):
        amount_usd = Decimal(str(amount_usd))
    if amount_usd <= Decimal('0'):
        raise ValueError("Deposit amount must be positive.")

    # 2. Validate profile_type
    valid_profile_types = [choice[0] for choice in WalletAccount.PROFILE_TYPE_CHOICES]
    if profile_type not in valid_profile_types:
        raise ValueError(f"Invalid profile_type. Choose from {valid_profile_types}")

    # 3. Check for existing active deposit
    existing_active = RefundableDeposit.objects.filter(
        user=user,
        profile_type=profile_type,
        deposit_type=deposit_type,
        status='active'
    ).select_for_update().first()

    if existing_active:
        if not allow_duplicate_active:
            if update_existing_if_duplicate:
                # Update the existing deposit with new amount and reference
                with transaction.atomic():
                    existing_active.amount_usd = amount_usd
                    existing_active.reference_code = reference_code
                    existing_active.notes = notes
                    if payable_item:
                        existing_active.related_payable_item = payable_item
                    existing_active.save(update_fields=[
                        'amount_usd', 'reference_code', 'notes',
                        'related_payable_item', 'updated_at'
                    ])
                return existing_active
            else:
                raise ValidationError(
                    f"An active {deposit_type} deposit already exists "
                    f"for this user with profile_type={profile_type}. "
                    "Use allow_duplicate_active=True if you intend to create another."
                )
        # If allowing duplicate, just continue to create new

    # 4. Create new deposit inside a transaction
    with transaction.atomic():
        deposit = RefundableDeposit.objects.create(
            user=user,
            profile_type=profile_type,
            deposit_type=deposit_type,
            amount_usd=amount_usd,
            related_payable_item=payable_item,
            reference_code=reference_code,
            notes=notes,
            status='active'
        )
    return deposit



# Recurring Rentals

def create_recurring_payable_item(
    *,
    user,
    profile_type,
    payment_for,
    title,
    amount_usd,
    description='',
    is_optional=False,
    due_date=None,
    effective_from=None,
    notes='',
    reference_code='',
):
    return UserPayableItem.objects.create(
        user=user,
        profile_type=profile_type,
        payment_for=payment_for,
        title=title,
        description=description,
        amount_state='confirmed',
        amount_usd=amount_usd,
        payment_status='due',
        is_active=True,
        is_optional=is_optional,
        due_date=due_date,
        effective_from=effective_from,
        reference_code=reference_code,
        notes=notes,
    )


def mark_foreigner_monthly_rental_paid(user, notes=''):
    monthly_item = get_active_payable_item(user, 'foreigner', 'monthly_rental')
    if not monthly_item:
        return False

    if monthly_item.payment_status != 'paid':
        now = timezone.now()
        monthly_item.payment_status = 'paid'
        monthly_item.paid_at = now
        if notes:
            monthly_item.notes = notes
        monthly_item.save(update_fields=['payment_status', 'paid_at', 'notes', 'updated_at'])

    # Credit rental owner wallet from this monthly rental payment
    credit_rental_owner_monthly_earning_from_guest_payment(
        user=user,
        monthly_item=monthly_item,
        notes=notes
    )

    # Decide next due date
    base_due_date = monthly_item.due_date or timezone.now().date()
    next_due_date = base_due_date + relativedelta(months=1)

    # Deactivate current active monthly item so new one can be created cleanly
    monthly_item.is_active = False
    monthly_item.save(update_fields=['is_active', 'updated_at'])

    # Create next month payable
    create_recurring_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='monthly_rental',
        title='Monthly Rental',
        amount_usd=monthly_item.amount_usd,
        description='Monthly rental amount for the next billing cycle.',
        is_optional=False,
        due_date=next_due_date,
        effective_from=next_due_date,
        notes='Auto-created for next monthly rental cycle.',
        reference_code=f'monthly_rental_{next_due_date.isoformat()}',
    )

    return True

def mark_foreigner_furniture_monthly_rental_paid(user, notes=''):
    furniture_item = get_active_payable_item(user, 'foreigner', 'furniture_rental')
    if not furniture_item:
        return False

    if furniture_item.payment_status != 'paid':
        now = timezone.now()
        furniture_item.payment_status = 'paid'
        furniture_item.paid_at = now
        if notes:
            furniture_item.notes = notes
        furniture_item.save(update_fields=['payment_status', 'paid_at', 'notes', 'updated_at'])

    base_due_date = furniture_item.due_date or timezone.now().date()

    next_due_date = base_due_date + relativedelta(months=1)

    furniture_item.is_active = False
    furniture_item.save(update_fields=['is_active', 'updated_at'])

    create_recurring_payable_item(
        user=user,
        profile_type='foreigner',
        payment_for='furniture_rental',
        title='Furniture Monthly Rental',
        amount_usd=furniture_item.amount_usd,
        description='Monthly furniture rental amount for the next billing cycle.',
        is_optional=True,
        due_date=next_due_date,
        effective_from=next_due_date,
        notes='Auto-created for next furniture rental cycle.',
        reference_code=f'furniture_rental_{next_due_date.isoformat()}',
    )

    return True

def mark_foreigner_payable_item_paid(user, payment_for, notes='', payable_item=None):
    """
    Common router for foreigner payable items.
    """

    if payment_for == 'account_confirmation':
        return mark_foreigner_confirmation_fee_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'featured_profile_fee':
        return mark_foreigner_featured_fee_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'one_time_service_fee':
        return mark_foreigner_service_fee_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'rental_deposit':
        return mark_foreigner_rental_deposit_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'furniture_rental_deposit':
        return mark_foreigner_furniture_deposit_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'monthly_rental':
        return mark_foreigner_monthly_rental_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'furniture_rental':
        return mark_foreigner_furniture_monthly_rental_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'host_event_booking':
        return mark_foreigner_host_event_booking_paid(
            user,
            notes=notes,
            payable_item=payable_item,
        )

    return False

def mark_foreigner_transaction_items_paid(payment_transaction, notes=''):
    """
    Marks all payable items inside a successful Guest/Foreigner PaymentTransaction as paid.

    Handles multiple selected payable items in one transaction.

    Known payment_for values use their own business logic:
    - featured_profile_fee
    - account_confirmation
    - one_time_service_fee
    - rental_deposit
    - furniture_rental_deposit
    - monthly_rental
    - furniture_rental
    - host_event_booking

    Unknown/simple payment_for values are marked as paid directly.
    """

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

    if payment_transaction.profile_type != 'foreigner':
        return {
            "success": False,
            "processed": [],
            "failed": ["Payment transaction is not for a Guest/Foreigner profile."],
        }

    payment_order = {
        'featured_profile_fee': 1,
        'account_confirmation': 2,
        'one_time_service_fee': 3,
        'rental_deposit': 4,
        'furniture_rental_deposit': 5,
        'monthly_rental': 6,
        'furniture_rental': 7,
        'host_event_booking': 8,
    }

    special_payment_types = set(payment_order.keys())

    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}"

        # -------------------------------------------------
        # Known business-rule payments
        # -------------------------------------------------
        if payment_for in special_payment_types:
            result = mark_foreigner_payable_item_paid(
                user=payment_transaction.user,
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )

        # -------------------------------------------------
        # Generic/simple payment types
        # -------------------------------------------------
        else:
            result = mark_user_payable_item_paid(
                user=payment_transaction.user,
                profile_type='foreigner',
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )

        payable_item.refresh_from_db()

        if result 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,
    }

def mark_dating_partner_confirmation_fee_paid(user, notes='', payable_item=None):
    """
    Mark dating confirmation fee as paid and update dating profile access flags.
    """
    payable_item = mark_user_payable_item_paid(
        user=user,
        profile_type='dating_partner',
        payment_for='dating_confirmation',
        notes=notes,
        payable_item=payable_item,
    )

    if not payable_item:
        return False

    dating_partner = getattr(user, 'dating_profile', None)

    if dating_partner:
        changed_fields = []

        if not dating_partner.is_confirmed:
            dating_partner.is_confirmed = True
            changed_fields.append('is_confirmed')

        if not dating_partner.can_book_arrangements:
            dating_partner.can_book_arrangements = True
            changed_fields.append('can_book_arrangements')

        if not dating_partner.can_view_private_listings:
            dating_partner.can_view_private_listings = True
            changed_fields.append('can_view_private_listings')

        if changed_fields:
            dating_partner.save(update_fields=changed_fields)

    return True


def mark_dating_partner_featured_fee_paid(user, notes='', payable_item=None):
    """
    Mark dating featured fee as paid and update featured flag.
    """
    payable_item = mark_user_payable_item_paid(
        user=user,
        profile_type='dating_partner',
        payment_for='dating_partner_featured',
        notes=notes,
        payable_item=payable_item,
    )

    if not payable_item:
        return False

    dating_partner = getattr(user, 'dating_profile', None)

    if dating_partner:
        changed_fields = []

        if not dating_partner.is_featured:
            dating_partner.is_featured = True
            changed_fields.append('is_featured')

        if changed_fields:
            dating_partner.save(update_fields=changed_fields)

    return True

def mark_dating_partner_payable_item_paid(user, payment_for, notes='', payable_item=None):
    """
    Router for Dating Partner payable item business logic.
    """

    if payment_for == 'dating_confirmation':
        return mark_dating_partner_confirmation_fee_paid(
            user=user,
            notes=notes,
            payable_item=payable_item,
        )

    if payment_for == 'dating_partner_featured':
        return mark_dating_partner_featured_fee_paid(
            user=user,
            notes=notes,
            payable_item=payable_item,
        )

    return False

def mark_dating_partner_transaction_items_paid(payment_transaction, notes=''):
    """
    Marks all payable items inside a successful Dating Partner PaymentTransaction as paid.
    """

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

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

    payment_order = {
        'dating_partner_featured': 1,
        'dating_confirmation': 2,
    }

    special_payment_types = set(payment_order.keys())

    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 in special_payment_types:
            result = mark_dating_partner_payable_item_paid(
                user=payment_transaction.user,
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )
        else:
            result = mark_user_payable_item_paid(
                user=payment_transaction.user,
                profile_type='dating_partner',
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )

        payable_item.refresh_from_db()

        if result 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,
    }

#==========================================================
#RENTAL OWNER
#=========================================================

def mark_rental_owner_featured_fee_paid(user, notes='', payable_item=None):
    """
    Mark rental owner featured / premium fee as paid,
    mark the owner as featured,
    and mark all related listings as featured + approved.
    """

    owner = getattr(user, 'rental_owner_profile', None)

    if not owner:
        return False

    paid_item = mark_user_payable_item_paid(
        user=user,
        profile_type='rental_owner',
        payment_for='rental_owner_featured',
        notes=notes,
        payable_item=payable_item,
    )

    if not paid_item:
        return False

    changed_fields = []

    if not owner.is_featured:
        owner.is_featured = True
        changed_fields.append('is_featured')

    if changed_fields:
        owner.save(update_fields=changed_fields)

    owner.listings.update(
        is_featured=True,
        is_approved=True
    )

    return True

def mark_rental_owner_payable_item_paid(user, payment_for, notes='', payable_item=None):
    """
    Router for Rental Owner payable item business logic.
    """

    if payment_for == 'rental_owner_featured':
        return mark_rental_owner_featured_fee_paid(
            user=user,
            notes=notes,
            payable_item=payable_item,
        )

    return False

def mark_rental_owner_transaction_items_paid(payment_transaction, notes=''):
    """
    Marks all payable items inside a successful Rental Owner PaymentTransaction as paid.
    """

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

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

    payment_order = {
        'rental_owner_featured': 1,
    }

    special_payment_types = set(payment_order.keys())

    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 in special_payment_types:
            result = mark_rental_owner_payable_item_paid(
                user=payment_transaction.user,
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )
        else:
            result = mark_user_payable_item_paid(
                user=payment_transaction.user,
                profile_type='rental_owner',
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )

        payable_item.refresh_from_db()

        if result 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,
    }

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

# credit rental owner wallet when monthly rental paid
def get_or_create_wallet_account(user, profile_type):
    wallet, created = WalletAccount.objects.get_or_create(
        user=user,
        profile_type=profile_type,
        defaults={
            'balance_usd': Decimal('0.00'),
            'is_active': True,
        }
    )
    return wallet

def create_wallet_transaction(
    *,
    wallet,
    transaction_type,
    title,
    amount_usd,
    is_credit=True,
    description='',
    related_payable_item=None,
    related_change_request=None,
    reference_code='',
    admin_notes='',
    status='completed',
):
    transaction = WalletTransaction.objects.create(
        wallet=wallet,
        transaction_type=transaction_type,
        status=status,
        title=title,
        description=description,
        amount_usd=amount_usd,
        is_credit=is_credit,
        related_payable_item=related_payable_item,
        related_change_request=related_change_request,
        reference_code=reference_code,
        admin_notes=admin_notes,
    )

    if status == 'completed':
        if is_credit:
            wallet.balance_usd += amount_usd
        else:
            wallet.balance_usd -= amount_usd

            if wallet.balance_usd < 0:
                wallet.balance_usd = Decimal('0.00')

        wallet.save(update_fields=['balance_usd', 'updated_at'])

    return transaction

def credit_rental_owner_monthly_earning_from_guest_payment(user, monthly_item, notes=''):
    """
    Credit rental owner's wallet when a foreigner monthly rental is paid.
    Net payout = guest monthly rental - platform commission.
    """
    foreigner = getattr(user, 'foreigner_profile', None)
    if not foreigner or not foreigner.lead:
        return None

    lead = foreigner.lead
    final_listing = getattr(lead, 'final_rental_property', None)
    if not final_listing or not final_listing.owner or not final_listing.owner.user:
        return None

    owner = final_listing.owner
    owner_user = owner.user

    settings_obj = get_platform_fee_settings()
    commission_percentage = Decimal('10.00')
    if settings_obj and settings_obj.rental_owner_monthly_fee_percentage is not None:
        commission_percentage = Decimal(str(settings_obj.rental_owner_monthly_fee_percentage))

    gross_amount = Decimal(str(monthly_item.amount_usd or 0))
    commission_amount = (gross_amount * commission_percentage / Decimal('100')).quantize(
        Decimal('0.01'),
        rounding=ROUND_HALF_UP
    )
    net_amount = (gross_amount - commission_amount).quantize(
        Decimal('0.01'),
        rounding=ROUND_HALF_UP
    )

    if net_amount <= Decimal('0.00'):
        return None

    wallet = get_or_create_wallet_account(owner_user, 'rental_owner')

    reference_code = monthly_item.reference_code or f"rental_income_{monthly_item.id}"

    description = (
        f"Rental income credited from guest monthly rental payment for listing "
        f'"{final_listing.title}". Gross: ${gross_amount}, '
        f'Commission ({commission_percentage}%): ${commission_amount}, '
        f'Net credited: ${net_amount}.'
    )

    transaction = create_wallet_transaction(
        wallet=wallet,
        transaction_type='rental_owner_income',
        title='Rental Owner Monthly Rental Earning',
        amount_usd=net_amount,
        is_credit=True,
        description=description,
        related_payable_item=monthly_item,
        reference_code=reference_code,
        admin_notes=notes,
        status='completed',
    )

    return transaction

def mark_service_partner_featured_fee_paid(user, notes='', payable_item=None):
    """
    Mark service partner featured / premium account fee as paid
    and update related service partner profile flags.
    """

    partner = getattr(user, 'service_partner_profile', None)

    if not partner:
        return False

    paid_item = mark_user_payable_item_paid(
        user=user,
        profile_type='service_partner',
        payment_for='service_partner_featured',
        notes=notes,
        payable_item=payable_item,
    )

    if not paid_item:
        return False

    changed_fields = []

    if not partner.is_featured:
        partner.is_featured = True
        changed_fields.append('is_featured')

    if not partner.is_public:
        partner.is_public = True
        changed_fields.append('is_public')

    if partner.status != 'approved':
        partner.status = 'approved'
        changed_fields.append('status')

    if changed_fields:
        partner.save(update_fields=changed_fields)

    return True

def approve_service_change_request(service_request, approved_by=None, admin_notes=''):
    """
    Approve and execute a ServiceChangeRequest.
    Returns True if handled successfully, otherwise False.
    """
    if not service_request:
        return False

    if service_request.profile_type != 'foreigner':
        return False

    foreigner = service_request.foreigner_profile
    if not foreigner:
        return False

    success = False

    if service_request.request_type == 'cancel_property_rental':
        success = handle_cancel_property_rental(service_request, foreigner)

    elif service_request.request_type == 'cancel_furniture_rental':
        success = handle_cancel_furniture_rental(service_request, foreigner)

    elif service_request.request_type == 'cancel_for_upgrade_package':
        success = handle_cancel_for_upgrade_package(service_request, foreigner)

    if not success:
        return False

    service_request.admin_approved = True
    service_request.status = 'approved'
    service_request.approved_by = approved_by
    service_request.reviewed_at = timezone.now()

    if admin_notes:
        if service_request.admin_notes:
            service_request.admin_notes += f"\n{admin_notes}"
        else:
            service_request.admin_notes = admin_notes

    service_request.save(update_fields=[
        'admin_approved',
        'status',
        'approved_by',
        'reviewed_at',
        'admin_notes',
    ])

    return True

def handle_cancel_property_rental(service_request, foreigner):
    user = service_request.user
    lead = getattr(foreigner, 'lead', None)
    if not lead:
        return False

    # Cancel future/unpaid rental items
    rental_items = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for__in=['rental_deposit', 'monthly_rental'],
        is_active=True
    ).exclude(payment_status='paid')

    for item in rental_items:
        item.payment_status = 'cancelled'
        item.is_active = False
        item.notes = 'Cancelled through approved property rental cancellation request.'
        item.save(update_fields=['payment_status', 'is_active', 'notes', 'updated_at'])

    # Unlock rental cycle
    foreigner.is_rental_cycle_active = False
    foreigner.last_rental_closed_at = timezone.now().date()
    foreigner.save(update_fields=['is_rental_cycle_active', 'last_rental_closed_at'])

    # Clear selected property
    lead.final_rental_property = None
    lead.final_rental_selected_at = None
    lead.save(update_fields=['final_rental_property', 'final_rental_selected_at'])

    return True

def handle_cancel_furniture_rental(service_request, foreigner):
    user = service_request.user
    lead = getattr(foreigner, 'lead', None)
    if not lead:
        return False

    furniture_items = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for__in=['furniture_rental_deposit', 'furniture_rental'],
        is_active=True
    ).exclude(payment_status='paid')

    for item in furniture_items:
        item.payment_status = 'cancelled'
        item.is_active = False
        item.notes = 'Cancelled through approved furniture rental cancellation request.'
        item.save(update_fields=['payment_status', 'is_active', 'notes', 'updated_at'])

    foreigner.is_furniture_cycle_active = False
    foreigner.last_furniture_closed_at = timezone.now().date()
    foreigner.save(update_fields=['is_furniture_cycle_active', 'last_furniture_closed_at'])

    lead.selected_furniture_package = None
    lead.save(update_fields=['selected_furniture_package'])

    return True

from decimal import Decimal


def handle_cancel_for_upgrade_package(service_request, foreigner):
    user = service_request.user
    lead = getattr(foreigner, 'lead', None)
    if not lead:
        return False

    new_package = service_request.requested_new_package
    if not new_package:
        return False

    current_package = lead.interested_package

    # Unlock main package for controlled upgrade flow
    foreigner.is_main_package_locked = False
    foreigner.save(update_fields=['is_main_package_locked'])

    # Switch to requested package
    lead.interested_package = new_package
    lead.save(update_fields=['interested_package'])

    # Cancel old unpaid upgrade items if any
    old_upgrade_items = UserPayableItem.objects.filter(
        user=user,
        profile_type='foreigner',
        payment_for='package_upgrade_fee',
        is_active=True
    ).exclude(payment_status='paid')

    for item in old_upgrade_items:
        item.payment_status = 'cancelled'
        item.is_active = False
        item.notes = 'Cancelled before new package upgrade recalculation.'
        item.save(update_fields=['payment_status', 'is_active', 'notes', 'updated_at'])

    # Calculate upgrade balance
    current_fee = Decimal('0.00')
    new_fee = Decimal('0.00')

    if current_package:
        current_fee = Decimal(str(current_package.service_fee_amount or 0))

    new_fee = Decimal(str(new_package.service_fee_amount or 0))
    balance_amount = new_fee - current_fee

    if balance_amount < 0:
        balance_amount = Decimal('0.00')

    if balance_amount > 0:
        create_or_update_user_payable_item(
            user=user,
            profile_type='foreigner',
            payment_for='package_upgrade_fee',
            title='Foreigner Service Upgrade Fee',
            amount_usd=balance_amount,
            amount_state='confirmed',
            payment_status='due',
            description='One-time facilitation and support upgrade fee based on the selected package.',
            is_optional=False,
        )

    # Lock again after controlled package reassignment
    foreigner.is_main_package_locked = True
    foreigner.save(update_fields=['is_main_package_locked'])

    return True

def complete_service_change_request(service_request, admin_notes=''):
    if not service_request:
        return False

    service_request.status = 'completed'
    service_request.completed_at = timezone.now()

    if admin_notes:
        if service_request.admin_notes:
            service_request.admin_notes += f"\n{admin_notes}"
        else:
            service_request.admin_notes = admin_notes

    service_request.save(update_fields=['status', 'completed_at', 'admin_notes'])
    return True

def top_up_wallet(
    user,
    amount_usd,
    profile_type,
    reference_code="",
    title="Wallet top-up",
    description="",
    admin_notes="",
):
    """
    Credit a specific wallet (by profile_type) after a successful card payment.

    Args:
        user: User instance (settings.AUTH_USER_MODEL)
        amount_usd (Decimal or float): Positive amount to add
        profile_type (str): One of WalletAccount.PROFILE_TYPE_CHOICES
        reference_code (str): Optional external reference (e.g., payment gateway tx id)
        title (str): Transaction title (default: "Wallet top-up")
        description (str): Optional detailed description
        admin_notes (str): Optional internal notes

    Returns:
        WalletTransaction: The created transaction record

    Raises:
        ValueError: If amount_usd <= 0
        ObjectDoesNotExist: If no active WalletAccount exists for the given user and profile_type
        ValidationError: If the wallet is inactive or other model validation fails
    """
    # 1. Validate amount
    if isinstance(amount_usd, float):
        amount_usd = Decimal(str(amount_usd))
    if amount_usd <= Decimal("0"):
        raise ValueError("Top-up amount must be positive.")

    # 2. Retrieve the target wallet (with row lock to avoid race conditions)
    with transaction.atomic():
        wallet = (
            WalletAccount.objects
            .select_for_update()
            .get(user=user, profile_type=profile_type, is_active=True)
        )

        # 3. Update wallet balance
        wallet.balance_usd += amount_usd
        wallet.full_clean()          # runs MinValueValidator etc.
        wallet.save()

        # 4. Create transaction record
        transaction_record = WalletTransaction.objects.create(
            wallet=wallet,
            transaction_type="topup",
            status="completed",
            title=title,
            description=description,
            amount_usd=amount_usd,
            is_credit=True,
            reference_code=reference_code,
            admin_notes=admin_notes,
        )

        return transaction_record
    
#Usage example
"""
try:
    tx = top_up_wallet(
        user=request.user,
        amount_usd=Decimal("49.99"),
        profile_type="foreigner",
        reference_code="stripe_tx_12345",
        title="Card top-up",
        description="Visa ending 4242",
    )
    # payment flow continues...
except ObjectDoesNotExist:
    # handle missing wallet – maybe create it on the fly
    pass
except ValueError as e:
    # handle invalid amount
    pass
    
"""
#property care app

def update_property_care_plan_payable_item(care_plan):
    """
    Backward-compatible helper for finalized Property Care Plans.

    Creates the first active monthly recurring payable item for a finalized
    PropertyCarePlan using the correct reference_code format:

        property_care_plan_<care_plan_id>_<YYYY-MM-DD>

    Important:
    - Only finalized care plans create payable items.
    - Only one active due monthly payable item should exist per care plan cycle.
    - Existing active payable item is reused, not duplicated.
    - Paid/cancelled/waived historical items are not overwritten.
    - The next monthly cycle is created later by mark_property_care_monthly_plan_fee_paid().
    """

    if not care_plan:
        return None

    if care_plan.plan_status != 'finalized':
        return None

    managed_property = getattr(care_plan, 'property', None)

    if not managed_property:
        return None

    property_owner_profile = getattr(managed_property, 'owner', None)

    if not property_owner_profile:
        return None

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

    if not user:
        return None

    final_monthly_fee = Decimal(str(care_plan.final_monthly_fee_usd or 0)).quantize(
        Decimal('0.01')
    )

    if final_monthly_fee <= Decimal('0.00'):
        return None

    # Use plan start date as the first billing cycle date.
    # If missing, safely fall back to today.
    due_date = care_plan.start_date or timezone.now().date()

    reference_code = f'property_care_plan_{care_plan.pk}_{due_date.isoformat()}'

    # Avoid duplicate active monthly payable item for this care plan.
    existing_active_item = UserPayableItem.objects.filter(
        user=user,
        profile_type='property_care_owner',
        payment_for='property_care_monthly_plan_fee',
        is_active=True,
        reference_code=reference_code,
    ).first()

    if existing_active_item:
        return existing_active_item

    # If another active item already exists for this same care plan, return it.
    # This prevents accidentally creating multiple active cycles for one plan.
    existing_plan_item = get_active_property_care_plan_payable_item(care_plan)

    if existing_plan_item:
        return existing_plan_item

    title = f'Property Care Monthly Plan Fee - {managed_property.property_name}'

    description = (
        f'Monthly property care fee for the finalized Home in Sri Lanka '
        f'care plan for "{managed_property.property_name}".'
    )

    payable_item = create_recurring_payable_item(
        user=user,
        profile_type='property_care_owner',
        payment_for='property_care_monthly_plan_fee',
        title=title,
        amount_usd=final_monthly_fee,
        description=description,
        is_optional=False,
        due_date=due_date,
        effective_from=due_date,
        notes=(
            f'Auto-created from finalized Property Care Plan #{care_plan.pk}. '
            f'Managed Property #{managed_property.pk}.'
        ),
        reference_code=reference_code,
    )

    return payable_item

def get_active_property_care_plan_payable_item(care_plan):
    """
    Get the currently active monthly payable item
    belonging to one finalized Property Care Plan.
    """

    if not care_plan:
        return None

    managed_property = care_plan.property
    owner_profile = managed_property.owner
    user = owner_profile.user

    reference_prefix = f'property_care_plan_{care_plan.pk}_'

    return UserPayableItem.objects.filter(
        user=user,
        profile_type='property_care_owner',
        payment_for='property_care_monthly_plan_fee',
        is_active=True,
        reference_code__startswith=reference_prefix,
    ).first()

def create_property_care_monthly_plan_payable_item(care_plan):
    """
    Creates the first due recurring monthly payable item
    for a finalized Property Care Plan.

    This should be called only after the plan is finalized.
    """

    if not care_plan:
        return None

    if care_plan.plan_status != 'finalized':
        return None

    managed_property = care_plan.property
    owner_profile = managed_property.owner
    user = owner_profile.user

    if not user:
        return None

    final_monthly_fee = Decimal(str(care_plan.final_monthly_fee_usd or 0))

    if final_monthly_fee <= 0:
        return None

    # Avoid duplicate first recurring payable if one already exists
    existing_active_item = get_active_property_care_plan_payable_item(care_plan)
    if existing_active_item:
        return existing_active_item

    due_date = care_plan.start_date or timezone.now().date()

    return create_recurring_payable_item(
        user=user,
        profile_type='property_care_owner',
        payment_for='property_care_monthly_plan_fee',
        title=f'Property Care Monthly Plan Fee - {managed_property.property_name}',
        amount_usd=final_monthly_fee,
        description=(
            f'Monthly property care fee for the finalized Home in Sri Lanka '
            f'care plan for "{managed_property.property_name}".'
        ),
        is_optional=False,
        due_date=due_date,
        effective_from=due_date,
        notes=(
            f'Auto-created from finalized Property Care Plan #{care_plan.pk}. '
            f'Managed Property #{managed_property.pk}.'
        ),
        reference_code=f'property_care_plan_{care_plan.pk}_{due_date.isoformat()}',
    )


def mark_property_care_monthly_plan_fee_paid(care_plan, notes=''):
    """
    Marks the current active monthly Property Care Plan payable item as paid,
    deactivates that billing-cycle item, and creates the next monthly due item.

    This is the recurring monthly billing equivalent of:
        mark_foreigner_monthly_rental_paid(...)

    Important:
    - Billing is tied to a specific PropertyCarePlan.
    - Each cycle is identified through the payable item's reference_code.
    - The next month's amount is carried forward from the paid payable item.
    """

    if not care_plan:
        return False

    if care_plan.plan_status != 'finalized':
        return False

    monthly_item = get_active_property_care_plan_payable_item(care_plan)

    if not monthly_item:
        return False

    # ---------------------------------------------------------
    # 1. Mark current monthly Property Care payable as paid
    # ---------------------------------------------------------
    if monthly_item.payment_status != 'paid':
        now = timezone.now()

        monthly_item.payment_status = 'paid'
        monthly_item.paid_at = now

        if notes:
            monthly_item.notes = notes

        monthly_item.save(
            update_fields=[
                'payment_status',
                'paid_at',
                'notes',
                'updated_at',
            ]
        )

    # ---------------------------------------------------------
    # 2. Decide next recurring monthly due date
    # ---------------------------------------------------------
    base_due_date = monthly_item.due_date or timezone.now().date()
    next_due_date = base_due_date + relativedelta(months=1)

    # ---------------------------------------------------------
    # 3. Deactivate current active payable item
    # ---------------------------------------------------------
    monthly_item.is_active = False
    monthly_item.save(
        update_fields=[
            'is_active',
            'updated_at',
        ]
    )

    # ---------------------------------------------------------
    # 4. Create the next monthly recurring payable item
    # ---------------------------------------------------------
    managed_property = care_plan.property

    create_recurring_payable_item(
        user=monthly_item.user,
        profile_type='property_care_owner',
        payment_for='property_care_monthly_plan_fee',
        title=f'Property Care Monthly Plan Fee - {managed_property.property_name}',
        amount_usd=monthly_item.amount_usd,
        description=(
            f'Monthly property care fee for the next billing cycle '
            f'for "{managed_property.property_name}".'
        ),
        is_optional=False,
        due_date=next_due_date,
        effective_from=next_due_date,
        notes='Auto-created for next Property Care monthly billing cycle.',
        reference_code=f'property_care_plan_{care_plan.pk}_{next_due_date.isoformat()}',
    )

    return True

def get_property_care_plan_from_payable_item(payable_item):
    """
    Resolve PropertyCarePlan from a property care payable item's reference code.

    Expected reference_code format:
        property_care_plan_<care_plan_id>_<YYYY-MM-DD>
    """

    if not payable_item:
        return None

    if payable_item.profile_type != 'property_care_owner':
        return None

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

    reference_code = payable_item.reference_code or ''

    match = re.match(
        r'^property_care_plan_(\d+)_\d{4}-\d{2}-\d{2}$',
        reference_code
    )

    if not match:
        return None

    care_plan_id = match.group(1)

    return PropertyCarePlan.objects.filter(
        pk=care_plan_id
    ).first()

def mark_property_care_owner_transaction_items_paid(payment_transaction, notes=''):
    """
    Marks all payable items inside a successful Property Care Owner PaymentTransaction as paid.

    Main supported payable item:
    - property_care_monthly_plan_fee

    For monthly plan fee, this resolves the PropertyCarePlan from the payable item's
    reference_code, marks the current monthly payable as paid, deactivates it,
    and creates the next recurring monthly payable item.
    """

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

    if payment_transaction.profile_type != 'property_care_owner':
        return {
            "success": False,
            "processed": [],
            "failed": ["Payment transaction is not for a Property Care Owner profile."],
        }

    payment_order = {
        'property_care_monthly_plan_fee': 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 == 'property_care_monthly_plan_fee':
            care_plan = get_property_care_plan_from_payable_item(payable_item)

            if not care_plan:
                failed.append(
                    f"Could not resolve PropertyCarePlan from payable item #{payable_item.id}."
                )
                continue

            result = mark_property_care_monthly_plan_fee_paid(
                care_plan=care_plan,
                notes=item_notes,
            )

            payable_item.refresh_from_db()

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

            continue

        # Fallback for any future property_care_owner payable item.
        paid_item = mark_user_payable_item_paid(
            user=payment_transaction.user,
            profile_type='property_care_owner',
            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,
    }


#================================================================
#Hosting Partner

def update_hosting_partner_payable_items(user):
    """
    Create or update optional Hosting Partner featured / premium visibility fee.
    This is the host-side payment to Ensolanka.
    """
    host_profile = getattr(user, 'host_profile', None)
    if not host_profile:
        return None

    settings_obj = get_platform_fee_settings()

    featured_fee = (
        Decimal(str(settings_obj.hosting_partner_featured_profile_fee_usd))
        if settings_obj and settings_obj.hosting_partner_featured_profile_fee_usd is not None
        else Decimal('5.00')
    )

    reference_code = f"HOST_FEATURED_{host_profile.id}"

    return create_or_update_user_payable_item(
        user=user,
        profile_type='hosting_partner',
        payment_for='hosting_partner_featured',
        title='Hosting Partner Featured / Premium Listing Fee',
        amount_usd=featured_fee,
        amount_state='confirmed',
        payment_status='due',
        description=(
            'Optional premium visibility fee to feature your Hosting Partner profile '
            'and improve the visibility of approved venues, event packages, meal plans, '
            'and add-on services.'
        ),
        is_optional=True,
        reference_code=reference_code,
        notes='Optional featured visibility payment for Host in Sri Lanka.',
    )

def get_active_hosting_partner_featured_payable_item(user):
    return get_active_payable_item(
        user=user,
        profile_type='hosting_partner',
        payment_for='hosting_partner_featured'
    )

@transaction.atomic
def mark_hosting_partner_featured_fee_paid(user, notes='', payable_item=None):
    host_profile = getattr(user, 'host_profile', None)

    if not host_profile:
        return False

    paid_item = mark_user_payable_item_paid(
        user=user,
        profile_type='hosting_partner',
        payment_for='hosting_partner_featured',
        notes=notes,
        payable_item=payable_item,
    )

    if not paid_item:
        return False

    if not host_profile.is_featured:
        host_profile.is_featured = True
        host_profile.save(update_fields=['is_featured'])

    public_venues = host_profile.venues.filter(is_public=True)

    public_venues.update(is_featured=True)

    for venue in public_venues:
        venue.event_packages.filter(is_active=True).update(is_featured=True)
        venue.meal_plans.filter(is_active=True).update(is_featured=True)
        venue.add_on_services.filter(is_active=True).update(is_featured=True)

    return True

def mark_hosting_partner_payable_item_paid(user, payment_for, notes='', payable_item=None):
    """
    Router for Hosting Partner payable item business logic.
    """

    if payment_for == 'hosting_partner_featured':
        return mark_hosting_partner_featured_fee_paid(
            user=user,
            notes=notes,
            payable_item=payable_item,
        )

    return False

def mark_hosting_partner_transaction_items_paid(payment_transaction, notes=''):
    """
    Marks all payable items inside a successful Hosting Partner PaymentTransaction as paid.
    """

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

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

    payment_order = {
        'hosting_partner_featured': 1,
    }

    special_payment_types = set(payment_order.keys())

    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 in special_payment_types:
            result = mark_hosting_partner_payable_item_paid(
                user=payment_transaction.user,
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )
        else:
            result = mark_user_payable_item_paid(
                user=payment_transaction.user,
                profile_type='hosting_partner',
                payment_for=payment_for,
                notes=item_notes,
                payable_item=payable_item,
            )

        payable_item.refresh_from_db()

        if result 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,
    }

def credit_hosting_partner_earning_from_event_booking_payment(
    *,
    booking,
    paid_item,
    notes='',
):
    """
    Credit hosting partner wallet when a guest/local client pays a hosting event booking.

    Gross = paid event booking amount
    Commission = platform commission %
    Net = gross - commission
    """
    if not booking:
        return None

    host_profile = getattr(booking, 'host_profile', None)
    if not host_profile or not host_profile.user:
        return None

    host_user = host_profile.user

    settings_obj = get_platform_fee_settings()

    commission_percentage = Decimal('10.00')
    if (
        settings_obj and
        settings_obj.hosting_partner_commission_percentage is not None
    ):
        commission_percentage = Decimal(
            str(settings_obj.hosting_partner_commission_percentage)
        )

    gross_amount = Decimal(str(paid_item.amount_usd or 0))

    commission_amount = (
        gross_amount * commission_percentage / Decimal('100')
    ).quantize(
        Decimal('0.01'),
        rounding=ROUND_HALF_UP
    )

    net_amount = (
        gross_amount - commission_amount
    ).quantize(
        Decimal('0.01'),
        rounding=ROUND_HALF_UP
    )

    if net_amount <= Decimal('0.00'):
        return None

    wallet = get_or_create_wallet_account(host_user, 'hosting_partner')

    reference_code = (
        paid_item.reference_code or
        f"hosting_event_income_{paid_item.id}"
    )

    venue_name = getattr(getattr(booking, 'venue', None), 'venue_name', 'Host venue')
    package_title = getattr(
        getattr(booking, 'selected_package', None),
        'title',
        'Hosting event booking'
    )

    description = (
        f'Hosting partner earning credited from event booking payment. '
        f'Venue: "{venue_name}". '
        f'Package: "{package_title}". '
        f'Gross: ${gross_amount}, '
        f'Commission ({commission_percentage}%): ${commission_amount}, '
        f'Net credited: ${net_amount}.'
    )

    transaction = create_wallet_transaction(
        wallet=wallet,
        transaction_type='hosting_partner_event_income',
        title='Hosting Partner Event Booking Earning',
        amount_usd=net_amount,
        is_credit=True,
        description=description,
        related_payable_item=paid_item,
        reference_code=reference_code,
        admin_notes=notes,
        status='completed',
    )

    return transaction

# Wallet Top up related

WALLET_TOPUP_ALLOWED_PROFILE_TYPES = [
    'foreigner',
    'dating_partner',
    'rental_owner',
    'service_partner',
    'property_care_owner',
]

def user_can_topup_wallet(user, profile_type):
    """
    Only allow wallet top-up for selected profile types.
    """

    if not user or not user.is_authenticated:
        return False

    if profile_type not in WALLET_TOPUP_ALLOWED_PROFILE_TYPES:
        return False

    if profile_type == 'foreigner':
        return getattr(user, 'foreigner_profile', None) is not None

    if profile_type == 'dating_partner':
        return (
            getattr(user, 'dating_profile', None) is not None or
            getattr(user, 'dating_partner_profile', None) is not None
        )

    if profile_type == 'rental_owner':
        return getattr(user, 'rental_owner_profile', None) is not None

    if profile_type == 'service_partner':
        return getattr(user, 'service_partner_profile', None) is not None

    if profile_type == 'property_care_owner':
        return getattr(user, 'property_manager_client_profile', None) is not None

    return False


def credit_wallet_from_direct_topup(
    *,
    user,
    profile_type,
    amount_usd,
    reference_code='',
    notes='',
):
    """
    Credit wallet after a successful direct wallet top-up payment.

    This does not require UserPayableItem.
    """

    if not user_can_topup_wallet(user, profile_type):
        return None

    amount_usd = Decimal(str(amount_usd or 0)).quantize(
        Decimal('0.01'),
        rounding=ROUND_HALF_UP
    )

    if amount_usd <= Decimal('0.00'):
        return None

    wallet = get_or_create_wallet_account(user, profile_type)

    transaction = create_wallet_transaction(
        wallet=wallet,
        transaction_type='topup',
        title='Wallet Top-up',
        amount_usd=amount_usd,
        is_credit=True,
        description=(
            f"Wallet credited from successful top-up payment. "
            f"Amount: USD {amount_usd}."
        ),
        related_payable_item=None,
        reference_code=reference_code,
        admin_notes=notes,
        status='completed',
    )

    return transaction

@transaction.atomic
def complete_wallet_topup_transaction(
    *,
    payment_transaction,
    gateway_session_id='',
    gateway_transaction_id='',
):
    """
    Complete a verified wallet top-up PaymentTransaction.

    This credits the wallet only once after Stripe/PayPal verification.
    """

    if not payment_transaction:
        return None

    locked_transaction = PaymentTransaction.objects.select_for_update().get(
        id=payment_transaction.id
    )

    if locked_transaction.payment_purpose != 'wallet_topup':
        return None

    if locked_transaction.status == 'paid':
        return locked_transaction

    amount_usd = Decimal(str(
        locked_transaction.gateway_amount_usd or locked_transaction.total_amount_usd or 0
    )).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

    if amount_usd <= Decimal('0.00'):
        locked_transaction.status = 'failed'
        locked_transaction.failure_reason = 'Invalid wallet top-up amount.'
        locked_transaction.failed_at = timezone.now()
        locked_transaction.save(update_fields=[
            'status',
            'failure_reason',
            'failed_at',
            'updated_at',
        ])
        return None

    wallet_transaction = credit_wallet_from_direct_topup(
        user=locked_transaction.user,
        profile_type=locked_transaction.profile_type,
        amount_usd=amount_usd,
        reference_code=gateway_transaction_id or gateway_session_id or f'wallet_topup_{locked_transaction.id}',
        notes=f'Wallet top-up completed from transaction #{locked_transaction.id}.',
    )

    if not wallet_transaction:
        locked_transaction.status = 'failed'
        locked_transaction.failure_reason = 'Wallet could not be credited.'
        locked_transaction.failed_at = timezone.now()
        locked_transaction.save(update_fields=[
            'status',
            'failure_reason',
            'failed_at',
            'updated_at',
        ])
        return None

    locked_transaction.gateway_session_id = gateway_session_id or locked_transaction.gateway_session_id
    locked_transaction.gateway_transaction_id = gateway_transaction_id or locked_transaction.gateway_transaction_id
    locked_transaction.status = 'paid'
    locked_transaction.paid_at = timezone.now()
    locked_transaction.failure_reason = ''
    locked_transaction.save(update_fields=[
        'gateway_session_id',
        'gateway_transaction_id',
        'status',
        'paid_at',
        'failure_reason',
        'updated_at',
    ])

    return locked_transaction