import { Request, Response } from "express";
import { z } from "zod";
import { PaymentMethod, PaymentAttemptStatus } from "@prisma/client";
import { asyncHandler } from "../../core/asyncHandler";
import { paymentService } from "./payment.service";
import { paymentRuntimeService } from "./payment-runtime.service";
import { FinalizeOrderPaymentSchema, ManualResolvePaymentAttemptSchema, StartPaymentAttemptSchema } from "ttm-shared";
import { prisma } from "@/core/prisma";
import { getPaymentProviderAdapter, hasCheckPaymentStatus } from "./providers/payment-provider.registry";
import { tracked } from "@/core/tracked";
import { paymentTerminalRealtimeService } from "./payment-terminal-realtime.service";

const OrderParamsSchema = z.object({
    id: z.string().uuid()
});

const AttemptParamsSchema = z.object({
    attemptId: z.string().uuid()
});

const PaymentParamsSchema = z.object({
    id: z.string().uuid(),
    paymentId: z.string().uuid(),
});

export const getPaymentOptions = asyncHandler(
    async (req: Request, res: Response) => {
        const { id } = OrderParamsSchema.parse(req.params);
        const stationId = (req.query.stationId as string) || null;

        const options = await paymentRuntimeService.getPaymentOptions(req.shop!.id, stationId);
        res.json(options);
    }
);

export const getOrderPaymentAttempts = asyncHandler(
    async (req: Request, res: Response) => {
        const { id } = OrderParamsSchema.parse(req.params);
        const attempts = await paymentRuntimeService.listOrderPaymentAttempts(req.shop!.id, id);
        res.json(attempts);
    }
);

export const startPaymentAttempt = asyncHandler(
    async (req: Request, res: Response) => {
        const { id } = OrderParamsSchema.parse(req.params);
        const input = StartPaymentAttemptSchema.parse(req.body);

        const result = await paymentRuntimeService.startPaymentAttempt(
            req.shop!.id,
            id,
            {
                ...input,
                method: input.method as PaymentMethod
            },
            req.senderSocketId
        );

        res.status(201).json(result);
    }
);

export const addPayment = asyncHandler(
    async (req: Request, res: Response) => {
        const { id } = OrderParamsSchema.parse(req.params);
        const { amount, method, reference, paymentAttemptId } = FinalizeOrderPaymentSchema.parse(req.body);

        const result = await paymentService.addPayment(
            req.shop!.id,
            id,
            amount,
            method as PaymentMethod,
            reference,
            paymentAttemptId,
            req.senderSocketId
        );

        res.json(result);
    }
);

export const cancelPayment = asyncHandler(
    async (req: Request, res: Response) => {
        const { id, paymentId } = PaymentParamsSchema.parse(req.params)

        const order = await paymentService.cancelPayment(
            req.shop!.id,
            id,
            paymentId,
            req.auth?.sub,
            req.senderSocketId,
        )

        res.json(order)
    }
)

export const checkPaymentAttemptStatus = asyncHandler(
    async (req: Request, res: Response) => {
        const { attemptId } = AttemptParamsSchema.parse(req.params);

        const attempt = await prisma.paymentAttempt.findUnique({
            where: { id: attemptId },
            include: {
                payment: true,
                accountPaymentProvider: true
            }
        });

        if (!attempt) {
            return res.status(404).json({ message: "Payment attempt not found" });
        }

        // Vérifier que l'attempt appartient au bon shop
        if (attempt.shopId !== req.shop!.id) {
            return res.status(403).json({ message: "Forbidden" });
        }

        // Si déjà terminé, retourner directement
        if (attempt.status !== PaymentAttemptStatus.PENDING) {
            return res.json({
                attemptId: attempt.id,
                orderId: attempt.orderId,
                status: attempt.status,
                providerTxId: attempt.providerTxId,
                failureReason: attempt.failureReason,
                paymentId: attempt.payment?.id ?? null
            });
        }

        // Si pas de provider ou pas de checkoutId, on ne peut pas vérifier
        if (!attempt.provider || !attempt.providerCheckoutId || !attempt.accountPaymentProvider) {
            return res.json({
                attemptId: attempt.id,
                orderId: attempt.orderId,
                status: "PENDING",
                providerTxId: null,
                failureReason: null,
                paymentId: null
            });
        }

        // Interroger le provider
        const adapter = getPaymentProviderAdapter(attempt.provider);

        if (!hasCheckPaymentStatus(adapter)) {
            return res.json({
                attemptId: attempt.id,
                orderId: attempt.orderId,
                status: "PENDING",
                providerTxId: null,
                failureReason: null,
                paymentId: null
            });
        }

        const checkResult = await adapter.checkPaymentStatus(
            attempt.providerCheckoutId,
            attempt.accountPaymentProvider,
            attempt.configSnapshot as Record<string, any> | null
        );

        if (checkResult.status === "NOT_POLLABLE") {
            return res.json({
                attemptId: attempt.id,
                orderId: attempt.orderId,
                status: "PENDING",
                providerTxId: null,
                failureReason: null,
                paymentId: null
            });
        }


        if (checkResult.status === "PAID" && !attempt.payment) {
            // Finaliser le paiement
            const result = await paymentService.addPayment(
                attempt.shopId,
                attempt.orderId,
                attempt.amount,
                attempt.method,
                checkResult.providerTxId ?? undefined,
                attempt.id,
                req.senderSocketId
            );

            return res.json({
                attemptId: attempt.id,
                orderId: attempt.orderId,
                status: "PAID",
                providerTxId: checkResult.providerTxId ?? null,
                failureReason: null,
                paymentId: result.paymentId
            });
        }

        if (checkResult.status === "FAILED") {
            await prisma.paymentAttempt.updateMany({
                where: { id: attempt.id, status: PaymentAttemptStatus.PENDING, payment: null },
                data: {
                    status: PaymentAttemptStatus.FAILED,
                    failureReason: checkResult.failureReason ?? "Provider reported failure"
                }
            });

            const t = tracked({ shopId: attempt.shopId, entity: "PaymentAttempt" });
            await t.emit(
                "payment.attempt_failed",
                {
                    paymentAttemptId: attempt.id,
                    orderId: attempt.orderId,
                    amount: attempt.amount,
                    method: attempt.method,
                    provider: attempt.provider,
                    stationId: attempt.stationId,
                    reason: checkResult.failureReason ?? "Provider reported failure"
                },
                attempt.id,
                1
            );

            await paymentTerminalRealtimeService.requestRefresh({
                shopId: attempt.shopId,
                deviceId: attempt.providerDeviceId,
                paymentAttemptId: attempt.id,
                orderId: attempt.orderId,
                stationId: attempt.stationId,
                senderSocketId: req.senderSocketId ?? null,
                reason: checkResult.failureReason ?? "Provider reported failure",
            })

            return res.json({
                attemptId: attempt.id,
                orderId: attempt.orderId,
                status: "FAILED",
                providerTxId: null,
                failureReason: checkResult.failureReason,
                paymentId: null
            });
        }

        return res.json({
            attemptId: attempt.id,
            orderId: attempt.orderId,
            status: "PENDING",
            providerTxId: null,
            failureReason: null,
            paymentId: null
        });
    }
);

export const manualResolvePaymentAttempt = asyncHandler(
    async (req: Request, res: Response) => {
        const { attemptId } = AttemptParamsSchema.parse(req.params);
        const input = ManualResolvePaymentAttemptSchema.parse(req.body);

        const result = await paymentRuntimeService.manualResolvePaymentAttempt(
            req.shop!.id,
            attemptId,
            input,
            req.senderSocketId
        );

        res.json(result);
    }
);

