"""
Service layer functions for payment methods.

Wires Payrix utilities to create/update payment methods from iframe tokens
and returns a unified response for the frontend.
"""
from __future__ import annotations

from typing import Any, Dict, Optional

from sqlalchemy import select
from sqlalchemy.orm import Session

from src.apps.payment_methods.models.payment_methods import PaymentMethod
from src.apps.payment_methods.models.payment_method_card_details import PaymentMethodCardDetails
from src.apps.payment_methods.models.payment_method_ach_details import PaymentMethodAchDetails
from src.apps.payment_methods.utils.payrix import (
    get_payment_token_details,
)
from src.apps.payment_methods.schemas.payment_methods import (
    PaymentMethodCardDetailsInput,
    PaymentMethodAchDetailsInput,
    PaymentMethodSaveResponseSchema,
    PaymentMethodListFilterSchema,
)
from src.apps.base.utils.functions import generate_secure_id
from src.helpers.pagination import QueryPaginator
from src.core.config import settings
from src.core.utils.constants import API_PREFIXES


# Removed legacy add_or_update_payment_method_from_token in favor of explicit save


async def get_payment_method_details_from_token(
    session: Session,
    *,
    payment_method_token: str,
    merchant_id: int,
    customer_id: Optional[int] = None,
    billing_info: Optional[Dict[str, Any]] = None,
    card_details: Optional[PaymentMethodCardDetailsInput] = None,
    ach_details: Optional[PaymentMethodAchDetailsInput] = None,
) -> Dict[str, Any]:
    """
    Fetch an existing Payrix token's details and return a unified response
    without creating/persisting a payment method locally.
    """
    normalized = await get_payment_token_details(
        session,
        token=payment_method_token,
        merchant_id=merchant_id,
        customer_id=customer_id,
    )

    # Do not overlay brand/last4/expiry/billing from payload; frontend owns these

    provider_id = str(normalized.get("provider_payment_method_id"))
    print("Provider ID:", provider_id)
    pm = session.execute(
        select(PaymentMethod).where(PaymentMethod.payment_method_id == provider_id)
    ).scalar_one_or_none()

    return {
        "provider": normalized.get("provider"),
        "provider_payment_method_id": provider_id,
        "brand": normalized.get("brand"),
        "last4": normalized.get("last4"),
        "exp_month": normalized.get("exp_month"),
        "exp_year": normalized.get("exp_year"),
        "billing": normalized.get("billing"),
        "payment_method_id_internal": pm.id if pm else None,
    }


async def save_payment_method(
    session: Session,
    *,
    payment_method_token: str,
    merchant_id: int,
    payment_method_id: Optional[str] = None,
    customer_id: Optional[int] = None,
    payer_id: Optional[int] = None,
    payment_request_id: Optional[int] = None,
    card_details: Optional[PaymentMethodCardDetailsInput] = None,
    ach_details: Optional[PaymentMethodAchDetailsInput] = None,
) -> PaymentMethodSaveResponseSchema:
    """
    Persist payment method details (card or ACH) and link them in payment_methods.

    customer_id, payer_id, and payment_request_id are now optional.
    Returns PaymentMethod with card_details or ach_details based on method type.
    """
    # Get provider token id from Payrix
    norm = await get_payment_token_details(
        session,
        token=payment_method_token,
        merchant_id=merchant_id,
        customer_id=customer_id,
    )
    provider_id = str(norm.get("provider_payment_method_id"))

    # Idempotency: if PaymentMethod already exists for this provider id, return it
    existing_pm = session.execute(
        select(PaymentMethod).where(PaymentMethod.payment_method_id == provider_id)
    ).scalar_one_or_none()
    if existing_pm:
        session.refresh(existing_pm)
        return PaymentMethodSaveResponseSchema.model_validate(existing_pm)

    pm_detail_id = None
    method = None

    if card_details is not None:
        method = "card"
        # Use payment_method_id from schema as reference_id, fallback to provider_id
        reference_id = payment_method_id if payment_method_id else provider_id
        card = PaymentMethodCardDetails(
            brand=card_details.brand,
            expire_month=str(card_details.exp_month) if card_details.exp_month else None,
            expire_year=str(card_details.exp_year) if card_details.exp_year else None,
            reference_id=reference_id,
            reference_token=payment_method_token,
            card_number=card_details.last4
        )
        session.add(card)
        session.flush()
        pm_detail_id = card.id

    elif ach_details is not None:
        method = "ach"
        # Use payment_method_id from schema as reference_id, fallback to provider_id
        reference_id = payment_method_id if payment_method_id else provider_id
        ach = PaymentMethodAchDetails(
            account_name=ach_details.account_name,
            bank_name=ach_details.bank_name,
            account_type=ach_details.account_type,
            routing_number=ach_details.routing_number,
            reference_id=reference_id,
            reference_token=payment_method_token,
        )
        session.add(ach)
        session.flush()
        pm_detail_id = ach.id

    else:
        # Nothing to persist without specific details - raise error
        raise ValueError("Either card_details or ach_details must be provided")

    # Create PaymentMethod linking to details
    pm = PaymentMethod(
        method=method,
        payment_method_id=generate_secure_id(prepend="pm", length=20),
        is_connected=True,
        scope="merchant",
        customer_id=customer_id,
        payer_id=payer_id,
        merchant_id=merchant_id,
        payment_request_id=payment_request_id,
        card_details_id=pm_detail_id if method == "card" else None,
        ach_details_id=pm_detail_id if method == "ach" else None,
    )
    session.add(pm)
    session.commit()
    session.refresh(pm)

    return PaymentMethodSaveResponseSchema.model_validate(pm)


async def get_payment_methods_list(
    db: Session,
    merchant_id: int,
    filters: PaymentMethodListFilterSchema,
    sort_by: str = "-created_at",
    skip: int = 0,
    limit: int = 20,
    paginate: bool = True,
) -> Dict[str, Any]:
    """
    Get payment methods list with filtering, sorting, and pagination.
    
    Args:
        db: Database session
        merchant_id: Merchant ID (required filter from router)
        filters: Filter parameters
        sort_by: Sort field with optional - prefix for descending
        skip: Number of records to skip
        limit: Number of records to return
        paginate: Whether to return paginated results
        
    Returns:
        Paginated payment methods or raw list
    """
    pagination_url: str = f"{settings.api_base_url()}{API_PREFIXES.PAYMENT_METHOD.value}"
    
    # Build base query
    stmt = select(PaymentMethod)
    if hasattr(PaymentMethod, 'deleted_at'):
        stmt = stmt.where(PaymentMethod.deleted_at.is_(None))
    
    # Required: merchant_id filter (always applied)
    stmt = stmt.where(PaymentMethod.merchant_id == merchant_id)
    
    # Optional filters
    if filters.search:
        search_term = f"%{filters.search}%"
        stmt = stmt.where(PaymentMethod.payment_method_id.ilike(search_term))
    
    if filters.method:
        stmt = stmt.where(PaymentMethod.method == filters.method)
    
    if filters.customer_id is not None:
        stmt = stmt.where(PaymentMethod.customer_id == filters.customer_id)
    
    if filters.payer_id is not None:
        stmt = stmt.where(PaymentMethod.payer_id == filters.payer_id)
    
    if filters.payment_request_id is not None:
        stmt = stmt.where(PaymentMethod.payment_request_id == filters.payment_request_id)
    
    if filters.scope:
        stmt = stmt.where(PaymentMethod.scope == filters.scope)
    
    # Boolean filter for is_connected
    if filters.is_connected != 0:  # 0 means "all"
        is_connected_bool = filters.is_connected == 1  # 1=connected, -1=not connected
        stmt = stmt.where(PaymentMethod.is_connected == is_connected_bool)
    
    # Apply sorting
    if sort_by.startswith("-"):
        field_name = sort_by[1:]
        if hasattr(PaymentMethod, field_name):
            stmt = stmt.order_by(getattr(PaymentMethod, field_name).desc())
    else:
        if hasattr(PaymentMethod, sort_by):
            stmt = stmt.order_by(getattr(PaymentMethod, sort_by))
    
    # Use QueryPaginator for pagination
    if paginate:
        paginator = QueryPaginator(
            query=stmt,
            schema=PaymentMethodSaveResponseSchema,
            url=pagination_url,
            db=db,
            model=PaymentMethod,
            offset=skip,
            limit=limit,
            use_orm=True,
        )
        
        return paginator.to_dict()
    else:
        # Return raw results without pagination
        results = db.execute(stmt.offset(skip).limit(limit)).scalars().all()
        return {
            "result": [PaymentMethodSaveResponseSchema.model_validate(pm) for pm in results]
        }
