import { AppError } from "@/core/AppError"
import { PaymentProvider } from "@prisma/client"
import {
    PAYMENT_PROVIDER_DEFINITIONS,
    type PaymentProviderInputMap,
    type PaymentProviderInputValue
} from "ttm-shared"
import type {
    PaymentProviderAdapter,
    ProviderAccountRecordLike,
    ValidateProviderAccountPayloadArgs,
    ValidatedProviderAccountPayload,
    CheckPaymentStatusResult,
    CancelAttemptResult
} from "./payment-provider.registry"

const definition = PAYMENT_PROVIDER_DEFINITIONS[PaymentProvider.SUMUP]

export interface SumUpResolvedAccountConfig {
    merchantCode: string
    payToEmail: string | null
    redirectUrl: string | null
    webhookUrl: string | null
}

export interface SumUpResolvedAccountSecrets {
    apiSecretKey: string
}

const SUMUP_API_BASE = "https://api.sumup.com"

type SumUpTxRecord = Record<string, any>

function normalizeStringValue(value: PaymentProviderInputValue | undefined): string | null {
    if (value === undefined || value === null) return null
    const trimmed = String(value).trim()
    return trimmed.length > 0 ? trimmed : null
}

function requireString(map: PaymentProviderInputMap, key: string): string {
    const value = map[key]
    if (typeof value !== "string" || value.trim().length === 0) {
        throw new AppError(`Missing SumUp field: ${key}`, 400)
    }
    return value.trim()
}

function optionalString(map: PaymentProviderInputMap, key: string): string | null {
    const value = map[key]
    if (typeof value !== "string") return null
    const trimmed = value.trim()
    return trimmed.length > 0 ? trimmed : null
}

function toInputMap(value: unknown): PaymentProviderInputMap {
    if (typeof value !== "object" || value === null || Array.isArray(value)) return {}
    return Object.entries(value).reduce<PaymentProviderInputMap>((acc, [key, raw]) => {
        if (raw === null || typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") {
            acc[key] = raw
        }
        return acc
    }, {})
}

function toRecord(value: unknown): Record<string, unknown> | null {
    if (typeof value !== "object" || value === null || Array.isArray(value)) return null
    return value as Record<string, unknown>
}

function extractTxRecord(payload: unknown): SumUpTxRecord | null {
    const asObject = toRecord(payload)
    if (!asObject) return null

    const maybeData = toRecord(asObject.data)
    if (maybeData) return maybeData as SumUpTxRecord

    return asObject as SumUpTxRecord
}

function extractTxItems(payload: unknown): SumUpTxRecord[] {
    const asObject = toRecord(payload)
    const maybeItems = asObject?.items
    if (Array.isArray(maybeItems)) return maybeItems.filter((item) => typeof item === "object" && item !== null) as SumUpTxRecord[]
    if (Array.isArray(payload)) return payload.filter((item) => typeof item === "object" && item !== null) as SumUpTxRecord[]
    return []
}

function txMatchesCheckoutId(tx: SumUpTxRecord, checkoutId: string): boolean {
    const candidates = [tx.client_transaction_id, tx.transaction_code, tx.internal_id, tx.transaction_id, tx.id]
    return candidates.some((value) => typeof value === "string" && value.trim() === checkoutId)
}

function toCheckPaymentStatusResult(tx: SumUpTxRecord): CheckPaymentStatusResult {
    const txStatus = typeof tx.status === "string" ? tx.status.toUpperCase() : ""

    if (txStatus === "SUCCESSFUL" || txStatus === "PAID") {
        return {
            status: "PAID",
            providerTxId: (tx.transaction_id ?? tx.id ?? tx.transaction_code ?? null) as string | null
        }
    }

    if (txStatus === "FAILED" || txStatus === "CANCELLED" || txStatus === "EXPIRED" || txStatus === "DECLINED") {
        return {
            status: "FAILED",
            failureReason: tx.status
        }
    }

    return { status: "PENDING" }
}

async function fetchSumUpJson(url: string, apiSecretKey: string): Promise<{ ok: boolean; status: number; body: unknown }> {
    const response = await fetch(url, {
        headers: {
            Authorization: `Bearer ${apiSecretKey}`,
        },
    })

    let body: unknown = null
    try {
        body = await response.json()
    } catch {
        body = null
    }

    return { ok: response.ok, status: response.status, body }
}

export const sumupPaymentProviderAdapter: PaymentProviderAdapter & {
    resolveSumUpAccount(account: ProviderAccountRecordLike): {
        config: SumUpResolvedAccountConfig
        secrets: SumUpResolvedAccountSecrets
    }
} = {
    provider: PaymentProvider.SUMUP,
    definition,
    validateAccountPayload(args: ValidateProviderAccountPayloadArgs): ValidatedProviderAccountPayload {
        const mergedConfig = {
            ...(args.existingConfig ?? {}),
            ...(args.config ?? {})
        }
        const mergedSecrets = {
            ...(args.existingSecrets ?? {}),
            ...(args.secrets ?? {})
        }

        const config: PaymentProviderInputMap = {
            merchantCode: normalizeStringValue(mergedConfig.merchantCode),
            payToEmail: normalizeStringValue(mergedConfig.payToEmail),
            redirectUrl: normalizeStringValue(mergedConfig.redirectUrl),
            webhookUrl: normalizeStringValue(mergedConfig.webhookUrl)
        }

        const secrets: PaymentProviderInputMap = {
            apiSecretKey: normalizeStringValue(mergedSecrets.apiSecretKey),
        }

        // Ne valider les champs requis que si on est en mode écriture (pas en mode lecture/listing)
        // En mode lecture, les secrets sont masqués et absents
        const isWriteMode = args.config !== undefined || args.secrets !== undefined
        if (isWriteMode) {
            if (!config.merchantCode) {
                throw new AppError("Le champ \"Merchant code\" est requis pour SumUp", 400)
            }

            if (!secrets.apiSecretKey) {
                throw new AppError("Le champ \"Api Secret Key\" est requis pour SumUp", 400)
            }
        }

        return { config, secrets }
    },
    readAccount(account: ProviderAccountRecordLike): ValidatedProviderAccountPayload {
        return this.validateAccountPayload({
            existingConfig: toInputMap(account.config),
            existingSecrets: toInputMap(account.secrets)
        })
    },
    getConfiguredSecretKeys(secrets: PaymentProviderInputMap) {
        return definition.fields
            .filter((field) => field.secret)
            .map((field) => field.key)
            .filter((key) => {
                const value = secrets[key]
                return value !== null && value !== undefined && String(value).trim().length > 0
            })
    },
    resolveSumUpAccount(account: ProviderAccountRecordLike) {
        const resolved = this.readAccount(account)
        return {
            config: {
                merchantCode: requireString(resolved.config, "merchantCode"),
                payToEmail: optionalString(resolved.config, "payToEmail"),
                redirectUrl: optionalString(resolved.config, "redirectUrl"),
                webhookUrl: optionalString(resolved.config, "webhookUrl")
            },
            secrets: {
                apiSecretKey: requireString(resolved.secrets, "apiSecretKey")
            }
        }
    },

    async checkPaymentStatus(checkoutId: string, account: ProviderAccountRecordLike, configSnapshot?: Record<string, any> | null): Promise<CheckPaymentStatusResult> {
        const resolved = this.resolveSumUpAccount(account)
        const sumupSnapshot = (configSnapshot as any)?.sumup as Record<string, any> | undefined

        // Reader flow: le providerCheckoutId stocke généralement client_transaction_id.
        // On tente d'abord l'endpoint transaction direct, puis l'historique en fallback.
        if (sumupSnapshot?.flowType === "reader") {
            const merchantCode = String(sumupSnapshot.merchantCode ?? resolved.config.merchantCode)
            const encodedCheckoutId = encodeURIComponent(checkoutId)

            console.log(`[SumUp checkStatus] Reader flow: lookup checkoutId=${checkoutId} merchant=${merchantCode}`)

            const transactionLookupUrls = [
                `${SUMUP_API_BASE}/v2.1/merchants/${encodeURIComponent(merchantCode)}/transactions?client_transaction_id=${encodedCheckoutId}`,
                `${SUMUP_API_BASE}/v2.1/merchants/${encodeURIComponent(merchantCode)}/transactions?transaction_code=${encodedCheckoutId}`,
                `${SUMUP_API_BASE}/v2.1/merchants/${encodeURIComponent(merchantCode)}/transactions?id=${encodedCheckoutId}`,
                `${SUMUP_API_BASE}/v2.1/merchants/${encodeURIComponent(merchantCode)}/transactions?internal_id=${encodedCheckoutId}`,
            ]

            for (const url of transactionLookupUrls) {
                const lookup = await fetchSumUpJson(url, resolved.secrets.apiSecretKey)

                if (!lookup.ok) {
                    if (lookup.status !== 404) {
                        console.warn(`[SumUp checkStatus] Transaction lookup failed (${lookup.status}) url=${url}`)
                    }
                    continue
                }

                const tx = extractTxRecord(lookup.body)
                if (!tx) continue

                if (txMatchesCheckoutId(tx, checkoutId)) {
                    console.log(`[SumUp checkStatus] Direct transaction match: status=${tx.status} tx_id=${tx.transaction_id ?? tx.id}`)
                    return toCheckPaymentStatusResult(tx)
                }
            }

            const historyLookupUrls = [
                `${SUMUP_API_BASE}/v2.1/merchants/${encodeURIComponent(merchantCode)}/transactions/history?transaction_code=${encodedCheckoutId}&order=descending&limit=20`,
                `${SUMUP_API_BASE}/v0.1/me/transactions/history?transaction_code=${encodedCheckoutId}&order=descending&limit=20`,
                `${SUMUP_API_BASE}/v0.1/me/transactions/history?order=descending&limit=20`,
            ]

            for (const url of historyLookupUrls) {
                const lookup = await fetchSumUpJson(url, resolved.secrets.apiSecretKey)

                if (!lookup.ok) {
                    console.warn(`[SumUp checkStatus] Transaction history lookup failed (${lookup.status}) url=${url}`)
                    continue
                }

                const items = extractTxItems(lookup.body)
                const tx = items.find((item) => txMatchesCheckoutId(item, checkoutId))

                console.log(`[SumUp checkStatus] History lookup got ${items.length} items url=${url}`)

                if (tx) {
                    console.log(`[SumUp checkStatus] History transaction match: status=${tx.status} tx_id=${tx.transaction_id ?? tx.id}`)
                    return toCheckPaymentStatusResult(tx)
                }
            }

            return { status: "PENDING" }
        }

        console.warn(`[SumUp checkStatus] Unknown flow type for checkoutId=${checkoutId}, returning NOT_POLLABLE`)
        return { status: "NOT_POLLABLE" }
    },

    async cancelAttempt(checkoutId: string, account: ProviderAccountRecordLike, configSnapshot?: Record<string, any> | null): Promise<CancelAttemptResult> {
        const resolved = this.resolveSumUpAccount(account)
        const sumupSnapshot = (configSnapshot as any)?.sumup as Record<string, any> | undefined

        if (sumupSnapshot?.flowType === "reader") {
            // Reader flow: terminate checkout on the reader via POST /v0.1/merchants/{mc}/readers/{readerId}/terminate
            const merchantCode = sumupSnapshot.merchantCode ?? resolved.config.merchantCode
            const readerId = sumupSnapshot.readerExternalId

            if (!readerId) {
                console.warn(`[SumUp cancelAttempt] No reader external ID in config snapshot, cannot abort reader checkout`)
                return { cancelled: false, reason: "Identifiant du terminal manquant" }
            }

            console.log(`[SumUp cancelAttempt] Terminating reader checkout: merchant=${merchantCode} reader=${readerId}`)

            try {
                const response = await fetch(
                    `https://api.sumup.com/v0.1/merchants/${encodeURIComponent(merchantCode)}/readers/${encodeURIComponent(readerId)}/terminate`,
                    {
                        method: "POST",
                        headers: {
                            Authorization: `Bearer ${resolved.secrets.apiSecretKey}`,
                            "Content-Type": "application/json",
                        },
                        body: JSON.stringify({}),
                    }
                )

                if (response.ok || response.status === 204) {
                    console.log(`[SumUp cancelAttempt] ✅ Reader checkout aborted successfully`)
                    return { cancelled: true }
                }

                // 404 = no active checkout on reader (already completed or not found)
                if (response.status === 404) {
                    console.log(`[SumUp cancelAttempt] Reader has no active checkout (404), treating as cancelled`)
                    return { cancelled: true, reason: "Aucune transaction active sur le terminal" }
                }

                const body = await response.text().catch(() => "")
                console.warn(`[SumUp cancelAttempt] Reader abort API error: ${response.status} ${body}`)
                return { cancelled: false, reason: `Erreur communication terminal (${response.status})` }
            } catch (err) {
                console.error(`[SumUp cancelAttempt] Reader abort request failed:`, err)
                return { cancelled: false, reason: "Impossible de joindre le terminal" }
            }
        }

        // Flow non-reader : annulation locale uniquement
        console.log(`[SumUp cancelAttempt] Non-reader flow: cancelling locally for checkoutId=${checkoutId}`)
        return { cancelled: true, reason: "Paiement annulé localement" }
    }
}

/**
 * Interroge l'API SumUp pour obtenir le statut d'un reader physique.
 * Utilise GET /v0.1/merchants/{mc}/readers/{readerId}/status
 * qui retourne le screen affiché (IDLE, WAITING_FOR_CARD…) + le device status (ONLINE/OFFLINE).
 */
export async function checkSumUpReaderAvailability(
    merchantCode: string,
    readerExternalId: string,
    apiSecretKey: string
): Promise<{ available: boolean; status: "ready" | "busy" | "offline" | "unknown"; reason: string | null }> {
    try {
        const response = await fetch(
            `https://api.sumup.com/v0.1/merchants/${encodeURIComponent(merchantCode)}/readers/${encodeURIComponent(readerExternalId)}/status`,
            {
                headers: {
                    Authorization: `Bearer ${apiSecretKey}`,
                },
            }
        )

        if (!response.ok) {
            console.warn(`[SumUp readerStatus] API error: ${response.status}`)
            if (response.status === 404) {
                return { available: false, status: "offline", reason: "Terminal introuvable" }
            }
            return { available: false, status: "unknown", reason: `Erreur API SumUp (${response.status})` }
        }

        const data = await response.json() as any
        // StatusResponse = { data: { status: "ONLINE"|"OFFLINE", state: "IDLE"|"WAITING_FOR_CARD"|..., ... } }
        const deviceStatus = (data?.data?.status ?? "").toUpperCase()
        const screenState = (data?.data?.state ?? "").toUpperCase()

        console.log(`[SumUp readerStatus] reader=${readerExternalId} device=${deviceStatus} state=${screenState}`)

        // Device status check
        if (deviceStatus === "OFFLINE" || deviceStatus === "DISCONNECTED") {
            return { available: false, status: "offline", reason: "Terminal hors ligne" }
        }

        // Screen / transaction state check
        // IDLE = ready, everything else means the reader is processing something
        // Known busy states: WAITING_FOR_CARD, WAITING_FOR_PIN, WAITING_FOR_SIGNATURE,
        //                    SELECTING_TIP, UPDATING_FIRMWARE
        if (screenState && screenState !== "IDLE") {
            const labels: Record<string, string> = {
                WAITING_FOR_CARD: "En attente de carte",
                WAITING_FOR_PIN: "En attente du code PIN",
                WAITING_FOR_SIGNATURE: "En attente de signature",
                SELECTING_TIP: "Sélection du pourboire",
                UPDATING_FIRMWARE: "Mise à jour en cours",
            }
            return {
                available: false,
                status: "busy",
                reason: labels[screenState] ?? "Terminal occupé"
            }
        }

        return { available: true, status: "ready", reason: null }
    } catch (err) {
        console.error(`[SumUp readerStatus] Request failed:`, err)
        return { available: false, status: "unknown", reason: "Impossible de vérifier le statut du terminal" }
    }
}

