import uuid
from urllib.parse import urlparse

from django.conf import settings
from django.db import transaction
from django.utils import timezone
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from transbank.common.integration_api_keys import IntegrationApiKeys
from transbank.common.integration_commerce_codes import IntegrationCommerceCodes
from transbank.common.integration_type import IntegrationType
from transbank.common.options import WebpayOptions
from transbank.webpay.webpay_plus.transaction import Transaction

from finanzas.models import Pago, Suscripciones

PAGO_PENDIENTE = "PENDING"
PAGO_APROBADO = "success"
PAGO_RECHAZADO = "failed"


def _transaction():
    # Ambiente de integración (pruebas) de Webpay Plus
    return Transaction(WebpayOptions(IntegrationCommerceCodes.WEBPAY_PLUS, IntegrationApiKeys.WEBPAY, IntegrationType.TEST))


def _return_url_permitida(url):
    """Evita redirecciones a sitios ajenos: el returnUrl debe ser de un origen del front permitido por CORS."""
    parsed = urlparse(url or "")
    origin = f"{parsed.scheme}://{parsed.netloc}"
    return parsed.scheme in ("http", "https") and origin in getattr(settings, "CORS_ALLOWED_ORIGINS", [])


@swagger_auto_schema(
    method='post',
    operation_description="Crea una transacción de Webpay para pagar mensualidades del usuario autenticado. "
                          "El monto se calcula en el servidor con el precio del plan.",
    tags=["Transbank"],
    security=[{'Bearer': []}],
    request_body=openapi.Schema(
        type=openapi.TYPE_OBJECT,
        properties={
            "meses": openapi.Schema(type=openapi.TYPE_INTEGER, description="Cantidad de mensualidades a pagar"),
            "returnUrl": openapi.Schema(type=openapi.TYPE_STRING, description="Url de retorno del front"),
        },
        required=["meses", "returnUrl"],
    ),
    responses={
        200: openapi.Response(description="Transacción creada"),
        400: openapi.Response(description="Datos inválidos"),
        404: openapi.Response(description="El usuario no tiene suscripción"),
        502: openapi.Response(description="Error de Transbank"),
    },
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def crearTransaccion(request):
    return_url = request.data.get("returnUrl")
    if not _return_url_permitida(return_url):
        return Response({"message": "URL de retorno no permitida"}, status=status.HTTP_400_BAD_REQUEST)

    suscripcion = Suscripciones.objects.select_related("planId").filter(userId=request.user).first()
    if suscripcion is None or suscripcion.planId is None:
        return Response({"message": "No tienes una suscripción activa"}, status=status.HTTP_404_NOT_FOUND)

    pendientes = max((suscripcion.totalMeses or 0) - (suscripcion.mesesPagados or 0), 0)
    try:
        meses = int(request.data.get("meses"))
    except (TypeError, ValueError):
        meses = 0
    if meses < 1 or meses > pendientes:
        return Response({"message": f"Debes pagar entre 1 y {pendientes} meses"}, status=status.HTTP_400_BAD_REQUEST)

    monto = suscripcion.planId.price * meses
    buy_order = uuid.uuid4().hex[:26]  # Transbank acepta hasta 26 caracteres
    try:
        resp = _transaction().create(buy_order=buy_order, session_id=str(request.user.id), amount=monto, return_url=return_url)
    except Exception as error:
        return Response({"message": "No se pudo crear la transacción", "error": str(error)}, status=status.HTTP_502_BAD_GATEWAY)

    Pago.objects.create(
        suscriptionId=suscripcion,
        monto=monto,
        moneda="CLP",
        status=PAGO_PENDIENTE,
        fechaPago=timezone.now(),
        transactionId=resp["token"],
        notas=f"Pago iniciado: {meses} mes(es), orden {buy_order}",
    )
    return Response({"message": "Transacción creada", "token": resp["token"], "url": resp["url"], "monto": monto},
                    status=status.HTTP_200_OK)


@swagger_auto_schema(
    method='post',
    operation_description="Confirma con Transbank la transacción (token_ws) y actualiza el pago y los meses pagados.",
    tags=["Transbank"],
    request_body=openapi.Schema(
        type=openapi.TYPE_OBJECT,
        properties={"token_ws": openapi.Schema(type=openapi.TYPE_STRING, description="Token devuelto por Webpay")},
        required=["token_ws"],
    ),
    responses={
        200: openapi.Response(description="Resultado de la transacción"),
        404: openapi.Response(description="Pago no encontrado"),
        502: openapi.Response(description="Error de Transbank"),
    },
)
@api_view(['POST'])
@permission_classes([AllowAny])  # El token_ws es secreto y está asociado a un pago creado en crearTransaccion
def confirmarTransaccion(request):
    token = request.data.get("token_ws")
    if not token:
        return Response({"message": "token_ws es obligatorio"}, status=status.HTTP_400_BAD_REQUEST)

    with transaction.atomic():
        pago = Pago.objects.select_for_update(of=("self",)).select_related("suscriptionId__planId").filter(transactionId=token).first()
        if pago is None:
            return Response({"message": "Pago no encontrado"}, status=status.HTTP_404_NOT_FOUND)

        # Idempotente: si ya se confirmó (p. ej. recarga de la página), se devuelve el resultado guardado
        if pago.status != PAGO_PENDIENTE:
            return Response(_resultado(pago, token, {}), status=status.HTTP_200_OK)

        try:
            resp = _transaction().commit(token=token)
        except Exception as error:
            pago.status = PAGO_RECHAZADO
            pago.notas = f"{pago.notas or ''} | Error al confirmar: {error}"
            pago.save(update_fields=["status", "notas"])
            return Response({"message": "No se pudo confirmar la transacción", "error": str(error)},
                            status=status.HTTP_502_BAD_GATEWAY)

        aprobado = resp.get("status") == "AUTHORIZED" and resp.get("response_code") == 0
        pago.status = PAGO_APROBADO if aprobado else PAGO_RECHAZADO
        pago.fechaPago = timezone.now()
        pago.notas = f"{pago.notas or ''} | {'Pago aprobado' if aprobado else 'Pago rechazado'}"
        pago.save(update_fields=["status", "fechaPago", "notas"])

        suscripcion = pago.suscriptionId
        if aprobado and suscripcion and suscripcion.planId and suscripcion.planId.price:
            meses = pago.monto // suscripcion.planId.price
            suscripcion.mesesPagados = min((suscripcion.mesesPagados or 0) + meses, suscripcion.totalMeses or 0)
            suscripcion.save(update_fields=["mesesPagados"])

    return Response(_resultado(pago, token, resp), status=status.HTTP_200_OK)


def _resultado(pago, token, resp):
    card = (resp.get("card_detail") or {}).get("card_number")
    return {
        "message": "Transacción confirmada",
        "status": pago.status,
        "amount": pago.monto,
        "transactionId": token,
        "buyOrder": resp.get("buy_order"),
        "authorizationCode": resp.get("authorization_code"),
        "paymentType": resp.get("payment_type_code"),
        "cardNumber": card,
        "date": pago.fechaPago.isoformat() if pago.fechaPago else None,
        "mesesPagados": pago.suscriptionId.mesesPagados if pago.suscriptionId else None,
    }
