"""
Payment Request services for business logic operations.
"""

import base64
import socket
from typing import Optional, List, Tuple, Any, Dict, Union
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from sqlalchemy import select
from datetime import datetime, timezone
from fastapi import Request, status
import logging

from src.apps.customers.services import get_customer, get_customer_contact
from src.core.exceptions import APIException
from src.apps.base.utils.functions import generate_secure_id
from src.apps.payment_requests.utils.functions import get_full_name, get_device_details
from src.core.utils.enums import (
    AuthorizationRecordTypes,
    PaymentTypes,
    TransactionSourceTypes,
    LimitationType,
    InvoiceStatusTypes,
    LogLevels
)
from src.apps.payment_requests.enums import (
    PaymentRequestStatusTypes,
    PaymentFrequencies,
    PaymentAuthorizationTypes,
    TaxType,
    SplitPaymentTypes,
    PaymentRequestStatusTypes as StatusTypes,
)
from src.apps.merchants.schemas.merchant_common import MerchantSchema
from src.apps.users.schemas.user_common import UserSchema
from src.apps.payment_requests.schemas.requests import MerchantPaymentRequestCreateSchema
from src.apps.payment_requests.schemas.responses import PaymentRequestResponseSchema
from src.apps.payment_requests.models.payment_request_authorizations import PaymentRequestAuthorizations
from src.apps.payment_requests.models.payment_request import PaymentRequest, payment_request_attachments_map
from src.apps.payment_requests.models.payment_request_line_items import PaymentRequestLineItems
from src.apps.payment_requests.utils.functions import round_half_up, get_final_amount, generate_unique_payment_request_literal
from src.apps.payment_requests.models.payment_request_adjustments import PaymentRequestAdjustments
from src.apps.customers.models.customer_contact import CustomerContact
from src.apps.payment_requests.models.payment_request_customer import PaymentRequestCustomer
from src.apps.payment_requests.models.partial_payment_request_configs import PartialPaymentRequestsConfigs
from src.apps.payment_requests.models.payment_request_authorizations import PaymentRequestAuthorizations
from src.apps._lib.utils.payrix_transactions import submit_payrix_charge
from src.apps.payment_methods.services import save_payment_method
from src.apps.payment_methods.schemas.payment_methods import (
    PaymentMethodCardDetailsInput,
    PaymentMethodAchDetailsInput,
)
from src.apps.payment_methods.models.payment_methods import PaymentMethod

logger = logging.getLogger(__name__)


async def create_payment_request(
    db: Session,
    merchant: MerchantSchema,
    current_user: UserSchema,
    payload: MerchantPaymentRequestCreateSchema,
    request: Request,
    payment_request_created_from: str = "USER",
    checkout_id: Optional[str] = None,
) -> PaymentRequestResponseSchema:
    """
    Create a new payment request
    
    Args:
        db: Database session
        merchant: Merchant information
        current_user: Current user information
        payload: Payment request creation data
        request: HTTP request object
        payment_request_created_from: Source of payment request creation
        checkout_id: Optional checkout ID
        
    Returns:
        PaymentRequestResponseSchema: Created payment request
        
    Raises:
        APIException: If validation or creation fails
    """
    # try:        
    # Check usage limitations
    # request_data = {
    #     "amount": payload.amount,
    #     "payment_methods": payload.payment_methods,
    #     "authorization_type": payload.authorization_type,
    # }
    
    # card_transaction_limit = await _check_usage_limitation(
    #     db=db,
    #     merchant=merchant,
    #     limitation_type=LimitationType.TRANSACTION_SIZE_LIMIT,
    #     request_data=request_data,
    # )
    # if card_transaction_limit:
    #     raise APIException(
    #         module=__name__,
    #         error={},
    #         status_code=status.HTTP_400_BAD_REQUEST,
    #         message=card_transaction_limit.get("message"),
    #     )

    # Prepare payment request parameters
    payment_request_params: Dict = {}
    errors: List[APIException] = []
    shipping_fee = round_half_up(payload.shipping_fee or 0)
    is_draft: bool = payload.is_draft

    # Validate and get customer
    customer = await get_customer(
        db=db, 
        customer_id=payload.customer_id
    )
    
    # TODO: Handle Tilled customer account creation
    # This would create tilled customer account if needed
    # Currently commented out as per requirements
    
    # Validate and get payer
    payer = await get_customer_contact(db=db, contact_id=payload.payer_id)
    
    # TODO: Handle Tilled payer account creation
    # This would create tilled payer account if needed
    # Currently commented out as per requirements

    # Create and attach payment methods
    pm_entities: List = []
    if payload.payment_methods and len(payload.payment_methods) > 0:
        for pm_data in payload.payment_methods:
            # Extract payment method details - now matches PaymentMethodSaveSchema structure
            payment_method_token = pm_data.payment_method_token
            payment_method_id = pm_data.payment_method_id
            card_details = pm_data.card_details
            ach_details = pm_data.ach_details
            
            # Save payment method (without payment_request_id initially)
            pm = await save_payment_method(
                session=db,
                payment_method_token=payment_method_token,
                merchant_id=merchant.id,
                payment_method_id=payment_method_id,
                customer_id=customer.id,
                payer_id=payer.id,
                payment_request_id=None,  # Will be updated after payment request creation
                card_details=card_details,
                ach_details=ach_details,
            )
            pm_entities.append(pm)

    # Setup payment request parameters
    payment_request_params.update({
        "amount": payload.amount,
        "currency": payload.currency,
        "payment_frequency": payload.payment_frequency,
        "authorization_type": payload.authorization_type,
        "is_surcharge_enabled": payload.is_surcharge_enabled,
        "surcharge_type": payload.surcharge_type,
        "reference": payload.reference,
        "due_date": payload.due_date,
        "billing_date": payload.billing_date,
        "save_payment_method": True,
        "allow_tip": payload.allow_tip,
        "require_billing_address": payload.require_billing_address,
        "require_sms_authorization": payload.require_sms_authorization,
        "require_shipping_address": payload.require_shipping_address,
        "require_signature_authorization": payload.require_signature_authorization,
        "require_cvv": payload.require_cvv,
        "is_certified": payload.is_certified,
        "tax_type": payload.tax_type,
        "configure_adjustment": payload.configure_adjustment,
        "message": payload.message,
        "payment_split_frequency": payload.payment_split_frequency,
        "description": payload.description,
        "tax_percent": payload.tax_percent,
        "shipping_fee": shipping_fee,
        "enable_email": payload.enable_email,
        "payment_redirect_url": payload.payment_redirect_url if payload.payment_redirect_url else None,
        "shipping_address_id": payload.shipping_address_id,
        "enable_sms": payload.enable_sms,
        "payment_requests_customers": {
            "payer_id": payload.payer_id if payload.payer_id else "",
            "approver_id": payload.approver_id if payload.approver_id else "",
            "payer_email_request_enabled": payload.payer_email_request_enabled,
            "payer_email_receipt_enabled": payload.payer_email_receipt_enabled,
            "payer_sms_request_enabled": payload.payer_sms_request_enabled,
            "payer_sms_receipt_enabled": payload.payer_sms_receipt_enabled,
            "approver_email_receipt_enabled": payload.approver_email_receipt_enabled,
            "approver_sms_receipt_enabled": payload.approver_sms_receipt_enabled,
        },
        "payment_adjustments": payload.payment_adjustments,
        "partial_payment": payload.partial_payment,
        "enable_email_receipt": payload.enable_email_receipt,
        "enable_sms_receipt": payload.enable_sms_receipt,
        "payment_type": PaymentTypes.RECEIVE_PAYMENT,
        "invoice_terms": None,  # TODO: Add invoice terms logic
    })

    # Set due date for different payment frequencies
    if payload.payment_frequency == PaymentFrequencies.SPLIT:
        payment_request_params["due_date"] = payload.split_config[0].due_date
    elif payload.payment_frequency == PaymentFrequencies.RECURRING:
        payment_request_params["due_date"] = payload.recurring_config.start_date
        if payload.recurring_config.prorate_first_payment:
            payment_request_params["due_date"] = payload.recurring_config.prorate_date

    # Set status for draft
    if is_draft:
        payment_request_params["status"] = PaymentRequestStatusTypes.DRAFT

    # Save the payment request
    new_paymentreq, msg = await save_payment_request(
        db=db,
        payload=payment_request_params,
        merchant=merchant,
        customer=customer,
        created_by=current_user,
    )

    if not new_paymentreq:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message=msg,
        )

    # Update payment_request_id in payment methods
    if pm_entities and len(pm_entities) > 0:
        for pm_entity in pm_entities:
            # Update the payment method with the payment request ID
            stmt = select(PaymentMethod).where(PaymentMethod.id == pm_entity.id)
            pm_record = db.execute(stmt).scalar_one_or_none()
            if pm_record:
                pm_record.payment_request_id = new_paymentreq.id
        db.commit()
        logger.info(f"Updated {len(pm_entities)} payment methods with payment request ID {new_paymentreq.payment_request_id}")

    # Create authorization record
    auth_record = await create_authorization_record(
        db=db, payment_request=new_paymentreq, payer=payer,
        merchant=merchant, customer=customer, current_user=current_user,
        payload=payload, request=request
    )


    # Save line items
    await save_line_items(
        db=db, line_items=payload.line_items, payment_request=new_paymentreq
    )


    # Save attachments
    if payload.attachments and len(payload.attachments) > 0:
        await save_attachments(
            db=db, attachments=payload.attachments, payment_request=new_paymentreq
        )

    # Submit payment charge to Payrix if payment methods are attached and not draft
    if not is_draft and pm_entities and len(pm_entities) > 0:
        # try:
        primary_payment_method = pm_entities[0]
        payment_method_type = primary_payment_method.method if hasattr(primary_payment_method, 'method') else 'card'
        
        # Get the Payrix token from reference_id in card_details or ach_details
        payrix_token = None
        if hasattr(primary_payment_method, 'card_details') and primary_payment_method.card_details:
            payrix_token = primary_payment_method.card_details.reference_id
        elif hasattr(primary_payment_method, 'ach_details') and primary_payment_method.ach_details:
            payrix_token = primary_payment_method.ach_details.reference_id
        
        if payrix_token:
            charge_result = await submit_payrix_charge(
                amount=float(new_paymentreq.amount),
                currency=new_paymentreq.currency.lower() if new_paymentreq.currency else "usd",
                customer_id=customer.id,
                payrix_token=payrix_token,
                payment_method_type=payment_method_type,
                merchant_id=merchant.id,
                capture=True,
            )
            
            logger.info(
                f"Payment charge submitted for payment request {new_paymentreq.payment_request_id}. "
                f"Transaction ID: {charge_result.get('transaction_id')}, Status: {charge_result.get('status')}"
            )
            
            # TODO: Create transaction record and update payment request status
            # This would store the transaction details returned from Payrix
                
        # except Exception as e:
        #     logger.error(f"Error submitting payment charge: {e}")
            # Continue with payment request creation even if charge fails
            # The charge can be retried later

    # Send payment request to message queue (if not draft)
    if not is_draft:
        # TODO: Emit task to message queue
        # This would send the payment request creation event to message queue
        # Currently commented out as per requirements
        pass

    # Return the response
    response = {}
    # try:
    response = PaymentRequestResponseSchema.model_validate(new_paymentreq)
    # except Exception:
    #     response = new_paymentreq._asdict() if hasattr(new_paymentreq, '_asdict') else new_paymentreq
    
    return response

    # except APIException:
    #     raise
    # except Exception as e:
    #     logger.error(f"Error creating payment request: {e}")
    #     raise APIException(
    #         module=__name__,
    #         error={"error": str(e)},
    #         status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
    #         message="Failed to create payment request",
    #     )




async def save_payment_request(
    db: Session, payload: Dict, merchant: MerchantSchema, 
    customer, created_by: UserSchema
) -> Tuple[Any, str]:
    """Save payment request to database"""
    # try:
    # Extract nested payloads before creating main payment request
    payment_customer_payload = payload["payment_requests_customers"]
    del payload["payment_requests_customers"]
    payment_adjustments = payload["payment_adjustments"]
    del payload["payment_adjustments"]
    partial_payment = payload["partial_payment"]
    del payload["partial_payment"]
    
    # Create main payment request
    payment_request = PaymentRequest(
        **payload,
        payment_request_id=generate_secure_id(prepend="pr", length=20),
        merchant_id=merchant.id,
        created_by_id=created_by.id,
        payment_request_literal=generate_unique_payment_request_literal(db=db, length=9),
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc),
    )
    db.add(payment_request)
    db.commit()
    db.refresh(payment_request)
    
    # Handle payment adjustments if provided
    if payment_adjustments:
        # from src.apps.discounts import discount_repository  # TODO: Import discount repository
        
        new_adjustment = payment_adjustments.dict().copy() if hasattr(payment_adjustments, 'dict') else payment_adjustments.copy()
        
        # Get Discount id
        get_discount_id = None
        if new_adjustment.get("discount_id"):
            # get_discount_id = discount_repository.get_one(db=db, discount_id=new_adjustment["discount_id"])  # TODO: Implement discount lookup
            pass
        
        if "discount_id" in new_adjustment:
            del new_adjustment["discount_id"]
        
        payment_request_adjustment = PaymentRequestAdjustments(
            **new_adjustment,
            payment_request_id=payment_request.id,
            discount_id=get_discount_id.id if get_discount_id else None,
        )
        db.add(payment_request_adjustment)
        db.commit()
    
    # Handle partial payment if provided
    if partial_payment:
        
        partial_payment_dict = partial_payment.dict().copy() if hasattr(partial_payment, 'dict') else partial_payment.copy()
        if "calculated_value" in partial_payment_dict:
            del partial_payment_dict["calculated_value"]
            
        payment_request_partial_details = PartialPaymentRequestsConfigs(
            **partial_payment_dict,
            payment_request_id=payment_request.id,
        )
        db.add(payment_request_partial_details)
        db.commit()
    
    # Handle payment request customers
    
    payer = None
    approver = None
    
    if payment_customer_payload.get("payer_id"):
        # SQLAlchemy 2.0 syntax for query
        payer_stmt = select(CustomerContact).where(
            CustomerContact.contact_id == payment_customer_payload["payer_id"],
            CustomerContact.customer_id == customer.id,
        )
        payer_result = db.execute(payer_stmt)
        payer = payer_result.scalar_one_or_none()
        
    if payment_customer_payload.get("approver_id"):
        # SQLAlchemy 2.0 syntax for query
        approver_stmt = select(CustomerContact).where(
            CustomerContact.contact_id == payment_customer_payload["approver_id"],
            CustomerContact.customer_id == customer.id,
        )
        approver_result = db.execute(approver_stmt)
        approver = approver_result.scalar_one_or_none()
    
    new_payment_customer = PaymentRequestCustomer(
        payment_request_id=payment_request.id,
        customer_id=customer.id,
        payer_id=payer.id if payer else None,
        approver_id=approver.id if approver else None,
        payer_email_request_enabled=payment_customer_payload.get("payer_email_request_enabled", False),
        payer_email_receipt_enabled=payment_customer_payload.get("payer_email_receipt_enabled", False),
        payer_sms_request_enabled=payment_customer_payload.get("payer_sms_request_enabled", False),
        payer_sms_receipt_enabled=payment_customer_payload.get("payer_sms_receipt_enabled", False),
        approver_email_receipt_enabled=payment_customer_payload.get("approver_email_receipt_enabled", False),
        approver_sms_receipt_enabled=payment_customer_payload.get("approver_sms_receipt_enabled", False),
        is_approver_approved=False if approver else True,
    )
    db.add(new_payment_customer)
    db.commit()
    db.refresh(new_payment_customer)
    db.refresh(payment_request)
    
    logger.info(f"Payment request {payment_request.payment_request_id} created successfully for merchant {merchant.id}")
    return payment_request, "Payment Request created"
    
    # except IntegrityError as e:
    #     logger.error(f"Database integrity error creating payment request: {e}")
    #     return None, "Payment request with this reference already exists"
    # except Exception as e:
    #     logger.error(f"Error creating payment request: {e}")
    #     return None, f"Failed to create payment request: {str(e)}"




async def create_authorization_record(
    db: Session, payment_request, payer, merchant: MerchantSchema,
    customer, current_user: UserSchema, payload: MerchantPaymentRequestCreateSchema,
    request: Request
):
    """Create authorization record for payment request"""
    
    try:
        # Get client information
        ip_address = socket.gethostbyname(request.client.host)
        user_agent_string = request.headers.get("user-agent", "")
        device_details = get_device_details(user_agent_string)
        # res_location = GeoLocationService.get(ip_address)  # TODO: Implement geolocation service
        
        # Generate unique authorization ID and literal
        authorization_id = generate_secure_id(prepend="auth", length=20)
        authorization_literal = f"AUTH-{generate_secure_id(length=8).upper()}"
        
        auth_payload = {
            "authorization_id": authorization_id,
            "authorization_literal": authorization_literal,
            "payer_id": payer.id,
            "merchant_id": merchant.id,
            "customer_id": customer.id,
            "payment_request_id": payment_request.id,
            "merchant_signer_id": current_user.id,
            "authorization_type": AuthorizationRecordTypes.CHECKBOX,
            "authorization_date": datetime.now(timezone.utc),
            "ip_address": ip_address,
            "auth_metadata": {
                "ip_addr": request.client.host,
                "user_agent": request.headers.get("user-agent", ""),
                "current_user": get_full_name(current_user),
                "current_user_id": current_user.user_id,
                "timestamp": datetime.now(timezone.utc).timestamp(),
                "os": device_details.get("os"),
                "device": device_details.get("device"),
                "platform": device_details.get("platform"),
                "browser": device_details.get("browser"),
                "origin": "",  # TODO: Implement geolocation to get country
                "txn_source": TransactionSourceTypes.API,
            },
            "created_at": datetime.now(timezone.utc),
            "updated_at": datetime.now(timezone.utc),
        }
        
        # Set authorization type based on request type and requirements
        if payment_request.authorization_type == PaymentAuthorizationTypes.REQUEST_AUTH:
            if payload.require_sms_authorization:
                auth_payload["authorization_type"] = AuthorizationRecordTypes.SMS
            elif payload.require_signature_authorization:
                auth_payload["authorization_type"] = AuthorizationRecordTypes.SIGNATURE
        else:
            auth_payload["is_verified"] =True

        # Save the authorization record to database
        auth_record = PaymentRequestAuthorizations(**auth_payload)
        db.add(auth_record)
        db.commit()
        db.refresh(auth_record)
        
        # Log the authorization creation
        # log_message = (
        #     f"{merchant.name} is requesting a {auth_payload['authorization_type']} authorization from {payer.name}"
        # )
        # if payment_request.authorization_type == PaymentAuthorizationTypes.PRE_AUTH:
        #     log_message = f"{merchant.name} has authorized a {auth_payload['authorization_type']} authorization on behalf of {get_full_name(customer)}"
        
        # log_payload = {
        #     "level": LogLevels.INFO,
        #     "event": APP_EVENTS["AUTHORIZATION"]["CREATE"],
        #     "authorization_id": auth_record.authorization_id,
        #     "authorization_type": "default",
        #     "message": log_message,
        #     "module": "authorization",
        #     "submodule": "authorization",
        #     "ip_address": ip_address,
        #     "created_by_id": current_user.id,
        #     "created_by": get_full_name(current_user),
        # }
        # await db_logger.log(DBLogCreate(**log_payload), False)
        
        return auth_record
        
    except Exception as e:
        logger.error(f"Error creating authorization record: {e}")
        raise APIException(
            module=__name__,
            error={"error": str(e)},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to create authorization record",
        )




async def save_line_items(db: Session, line_items: Optional[List], payment_request) -> None:
    """Save line items for payment request"""
    try:
        if line_items is not None and len(line_items) > 0:
            # Save each line item to database
            for index, item in enumerate(line_items):
                # Handle both Pydantic models and dicts
                if hasattr(item, 'dict'):
                    # Pydantic model - convert to dict
                    item_dict = item.dict()
                elif hasattr(item, '__dict__'):
                    # Try to use __dict__ for objects
                    item_dict = item.__dict__
                else:
                    # Assume it's already a dict
                    item_dict = item
                
                # Calculate tax amount if tax percentage is provided
                unit_price = float(item_dict.get("unit_price", 0))
                quantity = int(item_dict.get("quantity", 1))
                tax_percent = float(item_dict.get("tax", 0))
                discount = float(item_dict.get("discount", 0))
                
                # Calculate tax amount
                subtotal = unit_price * quantity
                discount_amount = discount if item_dict.get("discount_type") == "amount" else (subtotal * discount / 100)
                taxable_amount = subtotal - discount_amount
                tax_amount = taxable_amount * tax_percent / 100 if tax_percent > 0 else 0
                
                line_item_data = {
                    "title": item_dict.get("title", ""),
                    "description": item_dict.get("description"),
                    "unit_price": unit_price,
                    "quantity": quantity,
                    "tax": tax_percent,
                    "tax_amount": tax_amount,
                    "discount": discount,
                    "discount_type": item_dict.get("discount_type"),
                    "cost": item_dict.get("cost", 0),
                    "upcharge": item_dict.get("upcharge", 0),
                    "display_order": index + 1,
                    "product_id": item_dict.get("product_id"),
                    "payment_request_id": payment_request.id,
                }
                
                # Create and save line item
                line_item = PaymentRequestLineItems(**line_item_data)
                db.add(line_item)
                
            db.commit()
            logger.info(f"Saved {len(line_items)} line items for payment request {payment_request.payment_request_id}")
            
        else:
            # Create quick charge line item (no specific products, just the total amount)
            quick_charge_data = {
                "title": "Quick Charge",
                "description": "Payment request without specific line items",
                "unit_price": payment_request.amount,
                "quantity": 1,
                "tax": 0,
                "tax_amount": 0,
                "discount": 0,
                "cost": 0,
                "upcharge": 0,
                "display_order": 1,
                "payment_request_id": payment_request.id,
            }
            
            quick_charge_item = PaymentRequestLineItems(**quick_charge_data)
            db.add(quick_charge_item)
            db.commit()
            
            logger.info(f"Created quick charge line item for payment request {payment_request.payment_request_id}")
            
    except Exception as e:
        logger.error(f"Error saving line items for payment request {payment_request.payment_request_id}: {e}")
        raise APIException(
            module=__name__,
            error={"error": str(e)},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to save line items",
        )



async def save_attachments(db: Session, attachments: List[int], payment_request) -> None:
    """Save payment request attachments"""
    try:
        # Save attachments mapping to database
        for file_id in attachments:
            insert_stmt = payment_request_attachments_map.insert().values(
                payment_request_id=payment_request.id,
                file_id=file_id
            )
            db.execute(insert_stmt)
        
        db.commit()
        logger.info(f"Saved {len(attachments)} attachments for payment request {payment_request.payment_request_id}")
        
    except IntegrityError as e:
        logger.error(f"Integrity error saving attachments for payment request {payment_request.payment_request_id}: {e}")
        raise APIException(
            module=__name__,
            error={"error": str(e)},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Attachment already exists or invalid file ID",
        )
    except Exception as e:
        logger.error(f"Error saving attachments for payment request {payment_request.payment_request_id}: {e}")
        raise APIException(
            module=__name__,
            error={"error": str(e)},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to save attachments",
        )


async def update_payment_request(
    db: Session,
    payment_request_id: str,
    merchant: MerchantSchema,
    current_user: UserSchema,
    payload: Dict[str, Any],
    request: Request,
) -> PaymentRequestResponseSchema:
    """
    Update an existing payment request
    
    Args:
        db: Database session
        payment_request_id: Payment request ID to update
        merchant: Merchant information
        current_user: Current user information
        payload: Payment request update data (only non-null values)
        request: HTTP request object
        
    Returns:
        PaymentRequestResponseSchema: Updated payment request
        
    Raises:
        APIException: If validation or update fails
    """
    try:
        # Fetch existing payment request
        stmt = select(PaymentRequest).where(
            PaymentRequest.payment_request_id == payment_request_id,
            PaymentRequest.merchant_id == merchant.id
        )
        existing_payment_request = db.execute(stmt).scalar_one_or_none()
        
        if not existing_payment_request:
            raise APIException(
                module=__name__,
                error={},
                status_code=status.HTTP_404_NOT_FOUND,
                message="Payment request not found",
            )
        
        # Check if payment request can be updated
        if existing_payment_request.status in [PaymentRequestStatusTypes.PAID, PaymentRequestStatusTypes.CANCELLED]:
            raise APIException(
                module=__name__,
                error={},
                status_code=status.HTTP_400_BAD_REQUEST,
                message=f"Cannot update payment request with status: {existing_payment_request.status}",
            )
        
        is_draft = payload.get("is_draft", existing_payment_request.status == PaymentRequestStatusTypes.DRAFT)
        pm_entities: List = []
        
        # Handle customer update if provided
        customer = None
        if payload.get("customer_id"):
            customer = await get_customer(
                db=db, 
                customer_id=payload.get("customer_id")
            )
        else:
            # Fetch existing customer
            stmt = select(PaymentRequestCustomer).where(
                PaymentRequestCustomer.payment_request_id == existing_payment_request.id
            )
            pr_customer = db.execute(stmt).scalar_one_or_none()
            if pr_customer and pr_customer.customer_id:
                customer = await get_customer(db=db, customer_id=pr_customer.customer_id)
        
        # Handle payer update if provided
        payer = None
        if payload.get("payer_id"):
            payer = await get_customer_contact(db=db, contact_id=payload.get("payer_id"))
        
        # Handle payment methods if provided
        # Handle both Pydantic models and dicts for payload
        payment_methods = payload.payment_methods if hasattr(payload, 'payment_methods') else payload.get("payment_methods")
        
        if payment_methods and len(payment_methods) > 0:
            for pm_data in payment_methods:
                # Handle both Pydantic models and dicts for payment method data
                if hasattr(pm_data, 'payment_method_token'):
                    # Pydantic model - extract directly
                    payment_method_token = pm_data.payment_method_token
                    payment_method_id = pm_data.payment_method_id
                    card_details = pm_data.card_details
                    ach_details = pm_data.ach_details
                else:
                    # Dict - extract from dict and convert nested objects if needed
                    payment_method_token = pm_data.get("payment_method_token")
                    payment_method_id = pm_data.get("payment_method_id")
                    card_details_dict = pm_data.get("card_details")
                    ach_details_dict = pm_data.get("ach_details")
                    
                    # Convert nested dicts to Pydantic models if needed
                    card_details = None
                    if card_details_dict:
                        if hasattr(card_details_dict, 'brand'):
                            card_details = card_details_dict
                        else:
                            card_details = PaymentMethodCardDetailsInput(**card_details_dict) if card_details_dict else None
                    
                    ach_details = None
                    if ach_details_dict:
                        if hasattr(ach_details_dict, 'account_name'):
                            ach_details = ach_details_dict
                        else:
                            ach_details = PaymentMethodAchDetailsInput(**ach_details_dict) if ach_details_dict else None
                
                # Save payment method
                if payment_method_token:
                    pm = await save_payment_method(
                        session=db,
                        payment_method_token=payment_method_token,
                        merchant_id=merchant.id,
                        payment_method_id=payment_method_id,
                        customer_id=customer.id if customer else None,
                        payer_id=payer.id if payer else None,
                        payment_request_id=existing_payment_request.id,
                        card_details=card_details,
                        ach_details=ach_details,
                    )
                    pm_entities.append(pm)
        
        # Fetch existing payment methods if none provided
        if not pm_entities:
            stmt = select(PaymentMethod).where(
                PaymentMethod.payment_request_id == existing_payment_request.id
            )
            existing_pms = db.execute(stmt).scalars().all()
            pm_entities = list(existing_pms)
        
        # Update main payment request fields dynamically
        # Only update scalar columns (exclude PKs, FKs, timestamps, and relationships)
        from sqlalchemy.orm import class_mapper
        from sqlalchemy.orm.properties import RelationshipProperty
        
        mapper = class_mapper(PaymentRequest)
        
        # Get only scalar/column properties, exclude relationships
        scalar_columns = {
            prop.key for prop in mapper.iterate_properties
            if not isinstance(prop, RelationshipProperty)
        }
        
        # Define system-managed fields that should never be updated via API
        system_managed = {
            "id", "payment_request_id", "merchant_id", "created_by_id",
            "created_at", "updated_at", "deleted_at", "payment_request_literal"
        }
        
        # Fields that are updatable = scalar columns - system managed fields
        updatable_fields = scalar_columns - system_managed
        
        # Update all valid fields from payload
        for field, value in payload.items():
            if value is not None and field in updatable_fields:
                setattr(existing_payment_request, field, value)
        
        # Update status
        if "is_draft" in payload:
            if payload["is_draft"]:
                existing_payment_request.status = PaymentRequestStatusTypes.DRAFT
            elif existing_payment_request.status == PaymentRequestStatusTypes.DRAFT:
                existing_payment_request.status = PaymentRequestStatusTypes.PENDING
        
        existing_payment_request.updated_at = datetime.now(timezone.utc)
        db.commit()
        db.refresh(existing_payment_request)
        
        # Update line items if provided
        if payload.get("line_items"):
            # Delete existing line items
            db.query(PaymentRequestLineItems).filter(
                PaymentRequestLineItems.payment_request_id == existing_payment_request.id
            ).delete()
            db.commit()
            
            # Save new line items
            await save_line_items(
                db=db,
                line_items=payload["line_items"],
                payment_request=existing_payment_request
            )
        
        # Submit payment charge to Payrix if not draft and has payment methods
        if not is_draft and pm_entities and len(pm_entities) > 0:
            try:
                primary_payment_method = pm_entities[0]
                payment_method_type = primary_payment_method.method if hasattr(primary_payment_method, 'method') else 'card'
                
                # Get the Payrix token from reference_id in card_details or ach_details
                payrix_token = None
                if hasattr(primary_payment_method, 'card_details') and primary_payment_method.card_details:
                    payrix_token = primary_payment_method.card_details.reference_id
                elif hasattr(primary_payment_method, 'ach_details') and primary_payment_method.ach_details:
                    payrix_token = primary_payment_method.ach_details.reference_id
                
                if payrix_token and customer:
                    charge_result = await submit_payrix_charge(
                        amount=float(existing_payment_request.amount),
                        currency=existing_payment_request.currency.lower() if existing_payment_request.currency else "usd",
                        customer_id=customer.id,
                        payrix_token=payrix_token,
                        payment_method_type=payment_method_type,
                        merchant_id=merchant.id,
                        capture=True,
                    )
                    
                    logger.info(
                        f"Payment charge submitted for updated payment request {existing_payment_request.payment_request_id}. "
                        f"Transaction ID: {charge_result.get('transaction_id')}, Status: {charge_result.get('status')}"
                    )
                    
                    # TODO: Create transaction record and update payment request status
                    
            except Exception as e:
                logger.error(f"Error submitting payment charge on update: {e}")
                # Continue even if charge fails
        
        # Return the response
        response = PaymentRequestResponseSchema.model_validate(existing_payment_request)
        return response
        
    except APIException:
        raise
    except Exception as e:
        logger.error(f"Error updating payment request: {e}")
        raise APIException(
            module=__name__,
            error={"error": str(e)},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to update payment request",
        )