/**
 * Payment Attempt Poller
 *
 * Mécanisme de fallback côté backend pour résoudre le statut
 * d'un PaymentAttempt auprès du provider (ex: API SumUp)
 * quand le webhook ne répond pas ou n'est pas configuré.
 *
 * Flow:
 * 1. Après création d'un checkout → `startPolling(attemptId)`
 * 2. Le poller interroge l'API du provider à intervalles croissants
 * 3. Si le provider dit PAID → finalise le paiement, émet WS event
 * 4. Si le provider dit FAILED → met à jour, émet WS event
 * 5. Si le webhook arrive avant le poll → `stopPolling(attemptId)` (déjà résolu)
 * 6. Timeout max 3 minutes → arrête le poll, marque FAILED
 */

import { prisma } from "@/core/prisma"
import { PaymentAttemptStatus } from "@prisma/client"
import { getPaymentProviderAdapter, hasCheckPaymentStatus } from "./providers/payment-provider.registry"
import { paymentService } from "./payment.service"
import { tracked } from "@/core/tracked"
import { paymentTerminalRealtimeService } from "./payment-terminal-realtime.service"

// Premier tick plus tard pour laisser le checkout se créer côté provider
const INITIAL_DELAY = 5000
const POLL_INTERVALS = [5000, 5000, 5000, 8000, 8000, 10000, 10000, 15000, 15000]
const POLL_MAX_DURATION = 180_000 // 3 minutes
const MAX_WAITING_FOR_CHECKOUT_ID = 15_000 // 15s max pour attendre le checkoutId

interface PollEntry {
    timer: ReturnType<typeof setTimeout>
    attemptId: string
    pollIndex: number
    startedAt: number
}

const activePolls = new Map<string, PollEntry>()

export function startPolling(attemptId: string) {
    if (activePolls.has(attemptId)) return

    const entry: PollEntry = {
        timer: null as any,
        attemptId,
        pollIndex: 0,
        startedAt: Date.now()
    }

    activePolls.set(attemptId, entry)

    // Premier tick avec un délai initial plus long
    entry.timer = setTimeout(() => doPoll(entry), INITIAL_DELAY)

    console.log(`[PaymentPoller] Started polling for attempt ${attemptId} (first tick in ${INITIAL_DELAY}ms)`)
}

export function stopPolling(attemptId: string) {
    const entry = activePolls.get(attemptId)
    if (!entry) return

    clearTimeout(entry.timer)
    activePolls.delete(attemptId)

    console.log(`[PaymentPoller] Stopped polling for attempt ${attemptId}`)
}

function scheduleNext(entry: PollEntry) {
    const elapsed = Date.now() - entry.startedAt
    if (elapsed > POLL_MAX_DURATION) {
        console.warn(`[PaymentPoller] Timeout for attempt ${entry.attemptId}, stopping.`)
        handleTimeout(entry.attemptId)
        return
    }

    const interval = POLL_INTERVALS[Math.min(entry.pollIndex, POLL_INTERVALS.length - 1)]
    entry.timer = setTimeout(() => doPoll(entry), interval)
}

async function doPoll(entry: PollEntry) {
    const { attemptId } = entry

    // Si déjà retiré (webhook a résolu entre-temps)
    if (!activePolls.has(attemptId)) return

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

        if (!attempt) {
            console.log(`[PaymentPoller] attempt ${attemptId} not found in DB, stopping.`)
            stopPolling(attemptId)
            return
        }

        // Déjà résolu (par webhook ou autre)
        if (attempt.status !== PaymentAttemptStatus.PENDING) {
            console.log(`[PaymentPoller] attempt ${attemptId} already resolved (${attempt.status}), stopping.`)
            stopPolling(attemptId)
            return
        }

        // checkoutId pas encore écrit en base → attendre un peu
        if (!attempt.providerCheckoutId) {
            const elapsed = Date.now() - entry.startedAt
            if (elapsed > MAX_WAITING_FOR_CHECKOUT_ID) {
                console.warn(`[PaymentPoller] attempt ${attemptId} still has no checkoutId after ${elapsed}ms, stopping.`)
                handleTimeout(attemptId)
                return
            }
            console.log(`[PaymentPoller] attempt ${attemptId} checkoutId not yet available, retrying in 2s...`)
            entry.timer = setTimeout(() => doPoll(entry), 2000)
            return
        }

        if (!attempt.provider || !attempt.accountPaymentProvider) {
            console.log(`[PaymentPoller] attempt ${attemptId} missing provider or account, stopping.`)
            stopPolling(attemptId)
            return
        }

        const adapter = getPaymentProviderAdapter(attempt.provider)

        if (!hasCheckPaymentStatus(adapter)) {
            console.warn(`[PaymentPoller] Provider ${attempt.provider} has no checkPaymentStatus, stopping.`)
            stopPolling(attemptId)
            return
        }

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

        console.log(`[PaymentPoller] attempt=${attemptId} provider status=${result.status}`)

        if (result.status === "NOT_POLLABLE") {
            console.warn(`[PaymentPoller] attempt=${attemptId} provider flow is not pollable, stopping fallback poller.`)
            stopPolling(attemptId)
            return
        }


        if (result.status === "PAID" && !attempt.payment) {
            stopPolling(attemptId)

            await paymentService.addPayment(
                attempt.shopId,
                attempt.orderId,
                attempt.amount,
                attempt.method,
                result.providerTxId ?? undefined,
                attempt.id,
                null
            )

            console.log(`[PaymentPoller] ✅ Payment finalized for attempt ${attemptId}`)
            return
        }

        if (result.status === "FAILED") {
            stopPolling(attemptId)

            await prisma.paymentAttempt.updateMany({
                where: { id: attemptId, status: PaymentAttemptStatus.PENDING, payment: null },
                data: {
                    status: PaymentAttemptStatus.FAILED,
                    failureReason: result.failureReason ?? "Provider reported failure"
                }
            })

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

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

            console.log(`[PaymentPoller] ❌ Payment failed for attempt ${attemptId}`)
            return
        }

        // Toujours PENDING → continuer
        entry.pollIndex++
        scheduleNext(entry)
    } catch (err) {
        console.error(`[PaymentPoller] Error polling attempt ${attemptId}:`, err)
        entry.pollIndex++
        scheduleNext(entry)
    }
}

async function handleTimeout(attemptId: string) {
    stopPolling(attemptId)

    try {
        const attempt = await prisma.paymentAttempt.findUnique({
            where: { id: attemptId }
        })

        if (attempt && attempt.status === PaymentAttemptStatus.PENDING) {
            await prisma.paymentAttempt.updateMany({
                where: { id: attemptId, status: PaymentAttemptStatus.PENDING },
                data: {
                    status: PaymentAttemptStatus.FAILED,
                    failureReason: "Timeout: no response from payment provider"
                }
            })

            const t = tracked({ shopId: attempt.shopId, entity: "PaymentAttempt" })
            await t.emit(
                "payment.attempt_failed",
                {
                    paymentAttemptId: attemptId,
                    orderId: attempt.orderId,
                    amount: attempt.amount,
                    method: attempt.method,
                    provider: attempt.provider,
                    stationId: attempt.stationId,
                    reason: "Timeout: no response from payment provider"
                },
                attemptId,
                1
            )

            await paymentTerminalRealtimeService.requestRefresh({
                shopId: attempt.shopId,
                deviceId: attempt.providerDeviceId,
                paymentAttemptId: attempt.id,
                orderId: attempt.orderId,
                stationId: attempt.stationId,
                reason: "Timeout: no response from payment provider",
            })

            console.log(`[PaymentPoller] ⏰ Timeout for attempt ${attemptId}, marked as FAILED`)
        }
    } catch (err) {
        console.error(`[PaymentPoller] Timeout handling error for ${attemptId}:`, err)
    }
}

/** Nombre de polls actifs (debug) */
export function activePollCount() {
    return activePolls.size
}

