"""
Utilities for creating payment methods via Worldpay Payrix Pro.

Flow: Frontend iframe produces a temporary method token → we POST to
Payrix `/tokens` to create/save a token record → normalize fields and
persist a `PaymentMethod` + `PaymentMethodCardDetails` in our DB.

This module is pure (no FastAPI request objects). It accepts an
SQLAlchemy `Session` and necessary identifiers.
"""
from __future__ import annotations

import logging
from typing import Any, Dict, Optional

from sqlalchemy.orm import Session
from sqlalchemy import select

from src.core.exceptions import ValidationException, PayrixApiError
from src.core.payrix.http import payrix_request
from src.core.utils.enums import PaymentMethodTypes, PaymentMethodScopes
from src.apps.payment_methods.models.payment_methods import PaymentMethod
from src.apps.payment_methods.models.payment_method_card_details import (
    PaymentMethodCardDetails,
)

logger = logging.getLogger("payrix")


def _normalize_payrix_token(body: Dict[str, Any]) -> Dict[str, Any]:
    """
    Normalize Payrix token response into our standard shape.

    Note: Card meta (brand, last4, exp_month, exp_year) is provided by the
    frontend and not by Payrix for token lookups, so we do not attempt to
    extract those fields here. They are set to None and overlaid later.
    """
    provider_payment_method_id = (
        (body or {}).get("id")
        or (body or {}).get("tokenId")
        or (body or {}).get("token")
        or (body or {}).get("reference")
    )

    # Only include provider and token id; card meta comes from frontend
    return {
        "provider": "payrix",
        "provider_payment_method_id": provider_payment_method_id,
    }


async def create_payment_method_from_iframe_token(
    session: Session,
    *,
    token: str,
    # merchant_id: int,
    customer_id: Optional[int] = None,
    payer_id: Optional[int] = None,
    payment_request_id: Optional[int] = None,
    billing_info: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
    """
    Create and persist a payment method using a frontend Payrix iframe token.

    - Validates required input.
    - Calls Payrix `/tokens` to create/save the payment method token.
    - Normalizes the response and persists card details + payment method.

    Args:
        session: SQLAlchemy session (already configured and managed by caller)
        token: Iframe token from frontend
        merchant_id: Merchant identifier in our system
        customer_id: Customer identifier in our system
        payer_id: Optional payer/contact id
        payment_request_id: Optional payment request id
        billing_info: Optional fallback billing info to attach in normalized output

    Returns:
        A normalized dict with fields: provider, provider_payment_method_id,
        brand, last4, exp_month, exp_year, billing.

    Raises:
        ValidationException: if required inputs are missing
        PayrixApiError: if Payrix returns a non-2xx response
    """
    if not token or not isinstance(token, str):
        raise ValidationException(message="Token is required and must be a string")

    # Perform Payrix call (uses env-driven auth and base URL)
    resp = await payrix_request(
        operation="create_token",
        method="POST",
        path="/tokens",
        json={"token": token},
        # merchant_id=str(merchant_id),
        customer_id=str(customer_id),
        token="provided",
    )

    body: Dict[str, Any] = resp.json()
    normalized = _normalize_payrix_token(body)
    if billing_info and normalized.get("billing") is None:
        normalized["billing"] = billing_info

    if not normalized.get("provider_payment_method_id"):
        # If Payrix did not return an identifier, treat as validation failure
        raise ValidationException(
            message="Payrix did not return a token identifier",
            error=body,
            module="payrix",
        )

    # Idempotency: avoid duplicates by provider token id
    provider_id = str(normalized.get("provider_payment_method_id"))
    existing_pm = session.execute(
        select(PaymentMethod).where(PaymentMethod.payment_method_id == provider_id)
    ).scalar_one_or_none()

    if existing_pm:
        # logger.info(
        #     "provider=payrix op=%s merchant_id=%s customer_id=%s payment_method_id=%s idempotent_hit=1",
        #     "persist_payment_method",
        #     # merchant_id,
        #     customer_id,
        #     provider_id,
        # )
        return normalized

    # If customer/payer context not provided, do not persist; return normalized only
    if customer_id is None or payer_id is None:
        return normalized

    # Persist card details and payment method
    card = PaymentMethodCardDetails(
        brand=normalized.get("brand"),
        expire_month=str(normalized.get("exp_month")) if normalized.get("exp_month") else None,
        expire_year=str(normalized.get("exp_year")) if normalized.get("exp_year") else None,
        reference_id=provider_id,
    )

    session.add(card)
    session.flush()  # assigns `id`

    pm_kwargs = {
        "method": PaymentMethodTypes.CARD,
        "payment_method_id": provider_id,
        "is_connected": True,
        "scope": PaymentMethodScopes.MERCHANT,
        "customer_id": customer_id,
        "payer_id": payer_id,
        # "merchant_id": merchant_id,
        "card_details_id": card.id,
    }
    if payment_request_id is not None:
        pm_kwargs["payment_request_id"] = payment_request_id
    pm = PaymentMethod(**pm_kwargs)

    session.add(pm)
    session.commit()

    # logger.info(
    #     "provider=payrix op=%s merchant_id=%s customer_id=%s payment_method_id=%s",
    #     "persist_payment_method",
    #     # merchant_id,
    #     customer_id,
    #     normalized.get("provider_payment_method_id"),
    # )

    return normalized


async def get_payment_token_details(
    session: Session,
    *,
    token: str,
    merchant_id: int,
    customer_id: Optional[int] = None,
) -> Dict[str, Any]:
    """
    Retrieve details of a previously created Payrix token.

    This uses a GET call to the token resource and normalizes the response
    into our standard format without persisting anything to the database.

    Args:
        session: SQLAlchemy session (unused, kept for signature consistency)
        token: The token id/reference generated on the frontend
        merchant_id: Merchant identifier (used for auditing/routing on Payrix)
        customer_id: Optional customer identifier for logging/routing

    Returns:
        Normalized dict with provider, provider_payment_method_id, brand, last4,
        exp_month, exp_year, billing.
    """
    if not token or not isinstance(token, str):
        raise ValidationException(message="Token is required and must be a string")

    resp = await payrix_request(
        operation="get_token",
        method="GET",
        path=f"/tokens/{token}",
        merchant_id=str(merchant_id),
        customer_id=str(customer_id) if customer_id is not None else None,
    )

    body: Dict[str, Any] = resp.json()
    print("Payrix Token Details Response Body:", body)
    # Payrix response often nests data under { "response": { "data": [ {...} ] } }
    data_node: Dict[str, Any] | None = None
    if isinstance(body, dict) and "response" in body:
        resp_obj = body.get("response") or {}
        data_list = resp_obj.get("data") or []
        print("Data List:", data_list)

        if isinstance(data_list, list) and len(data_list) > 0:
            first = data_list[0]
            if isinstance(first, dict):
                data_node = first
    # Fallback to the top-level body if not nested
    normalized = _normalize_payrix_token(data_node if data_node is not None else body)
    return normalized
