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

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, 
            merchant_id=merchant.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

        # TODO: Create and attach payment methods
        # This section handles payment method preparation and attachment
        # Currently commented out as per requirements
        pm_entities: List = []
        # autosave_method = True if payment_request_created_from == "USER" else False
        # if payload.payment_methods and len(payload.payment_methods) > 0:
        #     pm_entities = await prepare_payment_methods_for_payment_request(
        #         db=db,
        #         customer=customer,
        #         merchant=merchant,
        #         authorization_type=payload.authorization_type,
        #         payload_payment_methods=payload.payment_methods,
        #         payer=payer,
        #         billing_address=payload.billing_address,
        #         autosave_method=autosave_method
        #     )

        # 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,
            )


        # 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
            )

        # 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):
                # Calculate tax amount if tax percentage is provided
                unit_price = float(item.get("unit_price", 0))
                quantity = int(item.get("quantity", 1))
                tax_percent = float(item.get("tax", 0))
                discount = float(item.get("discount", 0))
                
                # Calculate tax amount
                subtotal = unit_price * quantity
                discount_amount = discount if item.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.get("title", ""),
                    "description": item.get("description"),
                    "unit_price": unit_price,
                    "quantity": quantity,
                    "tax": tax_percent,
                    "tax_amount": tax_amount,
                    "discount": discount,
                    "discount_type": item.get("discount_type"),
                    "cost": item.get("cost", 0),
                    "upcharge": item.get("upcharge", 0),
                    "display_order": index + 1,
                    "product_id": item.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",
        )


