import { AppError } from "@/core/AppError"
import { createHash } from "crypto"
import { PaymentProvider } from "@prisma/client"
import {
    PAYMENT_PROVIDER_DEFINITIONS,
    type PaymentProviderAccountMetadataDto,
    type PaymentProviderAccountSummaryDto,
    type PaymentProviderInputMap,
    type PaymentProviderInputValue,
    type PaymentProviderConnectionDiagnosticDto,
    type PaymentProviderDiagnosticRawDto,
    type PaymentProviderRemoteDeviceDto,
    type PaymentProviderTerminalDiagnosticDto,
    type PaymentProviderWebhookDiagnosticDto,
    type PaymentProviderWebhookSimulationDiagnosticDto
} from "ttm-shared"
import type {
    PaymentProviderAdapter,
    ProviderAccountRecordLike,
    ProviderDiagnosticOptions,
    ProviderDeviceRecordLike,
    ProviderPendingAttemptLike,
    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"
const SUMUP_DIAGNOSTIC_TIMEOUT_MS = 5_000

type SumUpTxRecord = Record<string, any>
type SumUpReaderStatusKind = "ready" | "busy" | "offline" | "unknown"

interface SumUpReaderStatusDetails {
    available: boolean
    status: SumUpReaderStatusKind
    reason: string | null
    providerDeviceStatus: string | null
    providerScreenState: string | null
}

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 firstNonEmptyString(...candidates: unknown[]): string | null {
    for (const candidate of candidates) {
        if (typeof candidate === "string" && candidate.trim().length > 0) {
            return candidate.trim()
        }
    }

    return null
}

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 toSumUpRemoteReader(payload: unknown): PaymentProviderRemoteDeviceDto | null {
    const asObject = toRecord(payload)
    if (!asObject) return null

    const deviceRecord = toRecord(asObject.device)
    const readerRecord = toRecord(asObject.reader)
    const hardwareRecord = toRecord(asObject.hardware)
    const stateRecord = toRecord(asObject.state)

    const externalId = firstNonEmptyString(
        asObject.id,
        asObject.reader_id,
        asObject.external_id,
        deviceRecord?.id,
        readerRecord?.id,
    )

    if (!externalId) return null

    return {
        externalId,
        label: firstNonEmptyString(
            asObject.label,
            asObject.name,
            asObject.description,
            deviceRecord?.name,
            readerRecord?.name,
        ),
        model: firstNonEmptyString(
            asObject.model,
            asObject.device_model,
            deviceRecord?.model,
            hardwareRecord?.model,
        ),
        serialNumber: firstNonEmptyString(
            asObject.serial_number,
            asObject.serialNumber,
            deviceRecord?.serial_number,
            hardwareRecord?.serial_number,
        ),
        status: firstNonEmptyString(
            asObject.status,
            asObject.connection_status,
            deviceRecord?.status,
            stateRecord?.status,
        ),
    }
}

function extractSumUpRemoteReaders(payload: unknown): PaymentProviderRemoteDeviceDto[] {
    const asObject = toRecord(payload)
    const dataRecord = toRecord(asObject?.data)
    const collections = [
        Array.isArray(payload) ? payload : null,
        Array.isArray(asObject?.items) ? asObject.items : null,
        Array.isArray(asObject?.readers) ? asObject.readers : null,
        Array.isArray(asObject?.devices) ? asObject.devices : null,
        Array.isArray(dataRecord?.items) ? dataRecord.items : null,
        Array.isArray(dataRecord?.readers) ? dataRecord.readers : null,
        Array.isArray(dataRecord?.devices) ? dataRecord.devices : null,
        Array.isArray(asObject?.data) ? asObject.data : null,
    ]

    const seen = new Set<string>()
    const readers: PaymentProviderRemoteDeviceDto[] = []

    for (const collection of collections) {
        if (!Array.isArray(collection)) continue

        for (const item of collection) {
            const reader = toSumUpRemoteReader(item)
            if (!reader || seen.has(reader.externalId)) continue
            seen.add(reader.externalId)
            readers.push(reader)
        }
    }

    return readers
}

async function listSumUpRemoteReaders(
    merchantCode: string,
    apiSecretKey: string,
): Promise<PaymentProviderRemoteDeviceDto[]> {
    const candidateUrls = [
        `${SUMUP_API_BASE}/v0.1/merchants/${encodeURIComponent(merchantCode)}/readers`,
        `${SUMUP_API_BASE}/v0.1/me/readers`,
        `${SUMUP_API_BASE}/v0.1/readers`,
    ]

    for (const url of candidateUrls) {
        let response: { ok: boolean; status: number; body: unknown; headers: Record<string, string> }

        try {
            response = await fetchSumUpJson(url, apiSecretKey)
        } catch (error) {
            throw new AppError(
                error instanceof Error && error.message.trim().length > 0
                    ? `Impossible de joindre l'API SumUp pour récupérer les terminaux: ${error.message.trim()}`
                    : "Impossible de joindre l'API SumUp pour récupérer les terminaux",
                502,
            )
        }

        if (response.ok) {
            return extractSumUpRemoteReaders(response.body)
        }

        if (response.status === 404 || response.status === 405) {
            continue
        }

        throw new AppError(`Impossible de récupérer les terminaux SumUp (${response.status})`, 502)
    }

    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; headers: Record<string, string> }> {
    const controller = new AbortController()
    const timeout = setTimeout(() => controller.abort(), SUMUP_DIAGNOSTIC_TIMEOUT_MS)

    const response = await fetch(url, {
        headers: {
            Authorization: `Bearer ${apiSecretKey}`,
        },
        signal: controller.signal,
    })

    clearTimeout(timeout)

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

    return {
        ok: response.ok,
        status: response.status,
        body,
        headers: toHeadersRecord(response.headers),
    }
}

function toHeadersRecord(headers: Headers): Record<string, string> {
    const collected: Record<string, string> = {}
    headers.forEach((value, key) => {
        collected[key] = value
    })
    return collected
}

function maskSensitiveHeader(key: string, value: string): string {
    if (key.toLowerCase() === "authorization") {
        return /^Bearer\s+/iu.test(value) ? "Bearer ***" : "***"
    }

    return value
}

function buildDiagnosticRaw(options: {
    includeRaw?: boolean
    request?: {
        method: string
        url: string
        headers?: Record<string, string>
        body?: unknown
    } | null
    response?: {
        ok: boolean | null
        status: number | null
        headers?: Record<string, string>
        body?: unknown
    } | null
    notes?: string[]
}): PaymentProviderDiagnosticRawDto | null {
    if (!options.includeRaw) return null

    return {
        request: options.request ? {
            method: options.request.method,
            url: options.request.url,
            headers: Object.fromEntries(
                Object.entries(options.request.headers ?? {}).map(([key, value]) => [key, maskSensitiveHeader(key, value)])
            ),
            body: options.request.body ?? null,
        } : null,
        response: options.response ? {
            ok: options.response.ok,
            status: options.response.status,
            headers: options.response.headers ?? {},
            body: options.response.body ?? null,
        } : null,
        notes: options.notes ?? [],
    }
}

function buildTokenFingerprint(apiSecretKey: string): string {
    return `sha256:${createHash("sha256").update(apiSecretKey).digest("hex").slice(0, 12)}`
}

async function fetchUrlHealth(url: string): Promise<{ ok: boolean; status: number; body: string | null; headers: Record<string, string> }> {
    const controller = new AbortController()
    const timeout = setTimeout(() => controller.abort(), SUMUP_DIAGNOSTIC_TIMEOUT_MS)

    try {
        const response = await fetch(url, {
            method: "GET",
            headers: {
                "User-Agent": "TTM-Admin-SumUp-Diagnostic"
            },
            signal: controller.signal,
        })

        const body = await response.text().catch(() => null)
        return {
            ok: response.ok,
            status: response.status,
            body,
            headers: toHeadersRecord(response.headers),
        }
    } finally {
        clearTimeout(timeout)
    }
}

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

    const candidatePaths = [
        asObject.merchant_code,
        toRecord(asObject.merchant_profile)?.merchant_code,
        toRecord(asObject.data)?.merchant_code,
        toRecord(toRecord(asObject.data)?.merchant_profile)?.merchant_code,
    ]

    for (const candidate of candidatePaths) {
        if (typeof candidate === "string" && candidate.trim().length > 0) {
            return candidate.trim()
        }
    }

    return null
}

function extractSumUpAccountMetadata(
    payload: unknown,
): Omit<PaymentProviderAccountMetadataDto, "provider" | "schemaVersion" | "refreshedAt"> {
    const asObject = toRecord(payload)
    const merchantProfile = toRecord(asObject?.merchant_profile)
    const profile = toRecord(asObject?.profile)
    const merchantAddress = toRecord(merchantProfile?.address)
    const profileAddress = toRecord(profile?.address)
    const rootAddress = toRecord(asObject?.address)

    return {
        environment: null,
        companyName: firstNonEmptyString(
            asObject?.company_name,
            asObject?.business_name,
            asObject?.name,
            merchantProfile?.company_name,
            merchantProfile?.business_name,
            merchantProfile?.legal_name,
            merchantProfile?.name,
            profile?.company_name,
            profile?.business_name,
            profile?.name,
        ),
        email: firstNonEmptyString(
            asObject?.email,
            merchantProfile?.email,
            profile?.email,
        ),
        remoteMerchantCode: extractRemoteMerchantCode(payload),
        website: firstNonEmptyString(
            asObject?.website,
            merchantProfile?.website,
            profile?.website,
        ),
        city: firstNonEmptyString(
            asObject?.city,
            merchantProfile?.city,
            profile?.city,
            merchantAddress?.city,
            profileAddress?.city,
            rootAddress?.city,
        ),
        country: firstNonEmptyString(
            asObject?.country,
            merchantProfile?.country,
            profile?.country,
            merchantAddress?.country,
            profileAddress?.country,
            rootAddress?.country,
        ),
    }
}

export function resolveSumUpWebhookEndpoint(webhookBaseUrl: string | null | undefined): string | null {
    const normalized = webhookBaseUrl?.trim()
    if (!normalized) return null

    if (normalized.includes("/sumup/webhook")) {
        return normalized
    }

    return `${normalized.replace(/\/+$/u, "")}/api/sumup/webhook`
}

async function fetchSumUpReaderStatusDetails(
    merchantCode: string,
    readerExternalId: string,
    apiSecretKey: string
): Promise<SumUpReaderStatusDetails> {
    try {
        const response = await fetchSumUpJson(
            `${SUMUP_API_BASE}/v0.1/merchants/${encodeURIComponent(merchantCode)}/readers/${encodeURIComponent(readerExternalId)}/status`,
            apiSecretKey
        )

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

function interpretSumUpReaderStatusResponse(
    response: { ok: boolean | null; status: number | null; body: unknown },
    readerExternalId: string,
): SumUpReaderStatusDetails {
    if (!response.ok) {
        console.warn(`[SumUp readerStatus] API error: ${response.status ?? "unknown"}`)
        if (response.status === 404) {
            return {
                available: false,
                status: "offline",
                reason: "Terminal introuvable",
                providerDeviceStatus: null,
                providerScreenState: null,
            }
        }

        if (response.status === null) {
            return {
                available: false,
                status: "unknown",
                reason: "Impossible de vérifier le statut du terminal",
                providerDeviceStatus: null,
                providerScreenState: null,
            }
        }

        return {
            available: false,
            status: "unknown",
            reason: `Erreur API SumUp (${response.status})`,
            providerDeviceStatus: null,
            providerScreenState: null,
        }
    }

    const data = response.body as any
    const deviceStatus = typeof data?.data?.status === "string" ? data.data.status.toUpperCase() : null
    const screenState = typeof data?.data?.state === "string" ? data.data.state.toUpperCase() : null

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

    if (deviceStatus === "OFFLINE" || deviceStatus === "DISCONNECTED") {
        return {
            available: false,
            status: "offline",
            reason: "Terminal hors ligne",
            providerDeviceStatus: deviceStatus,
            providerScreenState: screenState,
        }
    }

    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é",
            providerDeviceStatus: deviceStatus,
            providerScreenState: screenState,
        }
    }

    return {
        available: true,
        status: "ready",
        reason: null,
        providerDeviceStatus: deviceStatus,
        providerScreenState: screenState,
    }
}

function buildTerminalDiagnosticMessage(status: SumUpReaderStatusKind, reason: string | null, pendingAttempt?: ProviderPendingAttemptLike | null) {
    if (pendingAttempt) {
        return `Transaction locale en attente (${pendingAttempt.orderId})`
    }

    if (reason?.trim()) return reason.trim()

    if (status === "ready") return "Terminal prêt et connecté"
    if (status === "offline") return "Terminal hors ligne"
    if (status === "busy") return "Terminal occupé"
    return "Statut du terminal indisponible"
}

function buildSimulatedSumUpWebhookPayload(account: ProviderAccountRecordLike, merchantCode: string) {
    const now = new Date()
    const diagnosticId = `${account.id ?? "sumup"}-${now.getTime()}`

    return {
        event_type: "solo.transaction.updated",
        merchant_code: merchantCode,
        created_at: now.toISOString(),
        payload: {
            id: `diag-event-${diagnosticId}`,
            client_transaction_id: `diag-checkout-${diagnosticId}`,
            transaction_id: `diag-tx-${diagnosticId}`,
            status: "SUCCESSFUL",
            amount: 123,
            currency: "EUR",
            card_type: "VISA",
            card_last_4_digits: "4242",
            merchant_code: merchantCode,
            timestamp: now.toISOString(),
        }
    }
}

async function postJson(url: string, payload: unknown): Promise<{ ok: boolean; status: number; body: unknown; headers: Record<string, string> }> {
    const controller = new AbortController()
    const timeout = setTimeout(() => controller.abort(), SUMUP_DIAGNOSTIC_TIMEOUT_MS)

    try {
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "User-Agent": "TTM-Admin-SumUp-Diagnostic"
            },
            body: JSON.stringify(payload),
            signal: controller.signal,
        })

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

        return {
            ok: response.ok,
            status: response.status,
            body,
            headers: toHeadersRecord(response.headers),
        }
    } finally {
        clearTimeout(timeout)
    }
}

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
            })
    },
    async getAccountMetadata(account: ProviderAccountRecordLike): Promise<PaymentProviderAccountMetadataDto | null> {
        const resolved = this.resolveSumUpAccount(account)
        const response = await fetchSumUpJson(`${SUMUP_API_BASE}/v0.1/me`, resolved.secrets.apiSecretKey)

        if (!response.ok) {
            throw new AppError(`Impossible de récupérer les informations du compte SumUp (${response.status})`, 502)
        }

        return {
            provider: PaymentProvider.SUMUP,
            schemaVersion: 1,
            refreshedAt: new Date().toISOString(),
            ...extractSumUpAccountMetadata(response.body),
        }
    },
    getAccountSummary(args: {
        account: ProviderAccountRecordLike
        metadata: PaymentProviderAccountMetadataDto | null
    }): PaymentProviderAccountSummaryDto | null {
        const resolved = this.resolveSumUpAccount(args.account)
        const location = [args.metadata?.city, args.metadata?.country].filter((value): value is string => Boolean(value)).join(", ")
        const details = [
            args.metadata?.companyName && args.metadata.companyName !== args.account.label ? args.metadata.companyName : null,
            args.metadata?.email,
            args.metadata?.website,
            location,
        ].filter((value): value is string => Boolean(value))

        return {
            title: args.account.label ?? args.metadata?.companyName ?? "Compte SumUp",
            subtitle: `Merchant ${resolved.config.merchantCode}`,
            details,
        }
    },
    async listRemoteDevices(account: ProviderAccountRecordLike): Promise<PaymentProviderRemoteDeviceDto[]> {
        const resolved = this.resolveSumUpAccount(account)
        return listSumUpRemoteReaders(resolved.config.merchantCode, resolved.secrets.apiSecretKey)
    },
    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 runConnectionDiagnostic(account: ProviderAccountRecordLike, options?: ProviderDiagnosticOptions): Promise<PaymentProviderConnectionDiagnosticDto> {
        const resolved = this.resolveSumUpAccount(account)
        const requestUrl = `${SUMUP_API_BASE}/v0.1/me`
        const tokenFingerprint = buildTokenFingerprint(resolved.secrets.apiSecretKey)
        const requestHeaders = {
            Authorization: `Bearer ${resolved.secrets.apiSecretKey}`,
        }

        try {
            const response = await fetchSumUpJson(requestUrl, resolved.secrets.apiSecretKey)
            const remoteMerchantCode = extractRemoteMerchantCode(response.body)
            const merchantCodeStatus = !remoteMerchantCode
                ? "unknown"
                : remoteMerchantCode === resolved.config.merchantCode
                    ? "match"
                    : "mismatch"

            if (!response.ok) {
                return {
                    accountId: account.id ?? "",
                    provider: PaymentProvider.SUMUP,
                    ok: false,
                    message: `Connexion SumUp refusée (${response.status})`,
                    testedAt: new Date().toISOString(),
                    remoteStatusCode: response.status,
                    merchantCode: resolved.config.merchantCode,
                    remoteMerchantCode,
                    merchantCodeStatus,
                    merchantCodeNote: merchantCodeStatus === "mismatch"
                        ? "Le merchant renvoyé par l'API ne correspond pas au merchant configuré. Le secret API stocké en base semble donc pointer vers un autre compte SumUp."
                        : null,
                    tokenFingerprint,
                    accountLabel: account.label ?? null,
                    raw: buildDiagnosticRaw({
                        includeRaw: options?.includeRaw,
                        request: {
                            method: "GET",
                            url: requestUrl,
                            headers: requestHeaders,
                        },
                        response: {
                            ok: response.ok,
                            status: response.status,
                            headers: response.headers,
                            body: response.body,
                        },
                        notes: [
                            "L'en-tête Authorization est masqué dans cette vue.",
                            "Le compte réellement testé est celui du secret API stocké en base pour ce provider.",
                             "Ce test valide l'accès API et compare le merchant renvoyé par SumUp au merchant configuré sur ce provider.",
                        ],
                    }),
                }
            }

            const merchantMismatch = remoteMerchantCode && remoteMerchantCode !== resolved.config.merchantCode

            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: true,
                message: merchantMismatch
                    ? `Connexion API valide mais merchant distant ${remoteMerchantCode} différent du merchant configuré ${resolved.config.merchantCode}`
                    : "Connexion SumUp valide",
                testedAt: new Date().toISOString(),
                remoteStatusCode: response.status,
                merchantCode: resolved.config.merchantCode,
                remoteMerchantCode,
                merchantCodeStatus: merchantMismatch ? "mismatch" : (remoteMerchantCode ? "match" : "unknown"),
                merchantCodeNote: merchantMismatch
                    ? "Le token testé ne semble pas pointer vers le compte attendu : le merchant distant renvoyé par /v0.1/me diffère du merchant configuré sur ce provider."
                    : null,
                tokenFingerprint,
                accountLabel: account.label ?? null,
                raw: buildDiagnosticRaw({
                    includeRaw: options?.includeRaw,
                    request: {
                        method: "GET",
                        url: requestUrl,
                        headers: requestHeaders,
                    },
                    response: {
                        ok: response.ok,
                        status: response.status,
                        headers: response.headers,
                        body: response.body,
                    },
                    notes: [
                        "L'en-tête Authorization est masqué dans cette vue.",
                        "Le compte réellement testé est celui du secret API stocké en base pour ce provider.",
                         "Ce test valide l'accès API et compare le merchant renvoyé par SumUp au merchant configuré sur ce provider.",
                    ],
                }),
            }
        } catch (error) {
            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: false,
                message: error instanceof Error && error.message.trim().length > 0
                    ? error.message.trim()
                    : "Impossible de joindre l'API SumUp",
                testedAt: new Date().toISOString(),
                remoteStatusCode: null,
                merchantCode: resolved.config.merchantCode,
                remoteMerchantCode: null,
                merchantCodeStatus: "unknown",
                merchantCodeNote: null,
                tokenFingerprint,
                accountLabel: account.label ?? null,
                raw: buildDiagnosticRaw({
                    includeRaw: options?.includeRaw,
                    request: {
                        method: "GET",
                        url: requestUrl,
                        headers: requestHeaders,
                    },
                    response: {
                        ok: null,
                        status: null,
                        body: {
                            error: error instanceof Error ? error.message : "Impossible de joindre l'API SumUp",
                        },
                    },
                    notes: [
                        "L'en-tête Authorization est masqué dans cette vue.",
                        "Le compte réellement testé est celui du secret API stocké en base pour ce provider.",
                         "Ce test valide l'accès API et compare le merchant renvoyé par SumUp au merchant configuré sur ce provider.",
                    ],
                }),
            }
        }
    },

    async runWebhookDiagnostic(account: ProviderAccountRecordLike, options?: ProviderDiagnosticOptions): Promise<PaymentProviderWebhookDiagnosticDto> {
        const resolved = this.resolveSumUpAccount(account)
        const webhookUrl = resolveSumUpWebhookEndpoint(resolved.config.webhookUrl)
        const requestUrl = webhookUrl
            ? `${webhookUrl}${webhookUrl.includes("?") ? "&" : "?"}diagnostic=1`
            : null

        if (!webhookUrl) {
            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: false,
                message: "Aucune URL de webhook publique n'est configurée pour ce compte SumUp",
                testedAt: new Date().toISOString(),
                webhookUrl: null,
                httpStatus: null,
                responseTimeMs: null,
                raw: null,
            }
        }

        const startedAt = Date.now()

        try {
            const response = await fetchUrlHealth(requestUrl!)
            const responseTimeMs = Date.now() - startedAt

            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: response.ok,
                message: response.ok
                    ? `Webhook joignable (${response.status})`
                    : `Webhook joignable mais réponse inattendue (${response.status})`,
                testedAt: new Date().toISOString(),
                webhookUrl,
                httpStatus: response.status,
                responseTimeMs,
                raw: buildDiagnosticRaw({
                    includeRaw: options?.includeRaw,
                    request: {
                        method: "GET",
                        url: requestUrl!,
                        headers: {
                            "User-Agent": "TTM-Admin-SumUp-Diagnostic",
                        },
                    },
                    response: {
                        ok: response.ok,
                        status: response.status,
                        headers: response.headers,
                        body: response.body,
                    },
                }),
            }
        } catch (error) {
            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: false,
                message: error instanceof Error && error.message.trim().length > 0
                    ? error.message.trim()
                    : "Impossible de tester le webhook public",
                testedAt: new Date().toISOString(),
                webhookUrl,
                httpStatus: null,
                responseTimeMs: Date.now() - startedAt,
                raw: buildDiagnosticRaw({
                    includeRaw: options?.includeRaw,
                    request: {
                        method: "GET",
                        url: requestUrl!,
                        headers: {
                            "User-Agent": "TTM-Admin-SumUp-Diagnostic",
                        },
                    },
                    response: {
                        ok: null,
                        status: null,
                        body: {
                            error: error instanceof Error ? error.message : "Impossible de tester le webhook public",
                        },
                    },
                }),
            }
        }
    },

    async runWebhookSimulationDiagnostic(account: ProviderAccountRecordLike, options?: ProviderDiagnosticOptions): Promise<PaymentProviderWebhookSimulationDiagnosticDto> {
        const resolved = this.resolveSumUpAccount(account)
        const webhookUrl = resolveSumUpWebhookEndpoint(resolved.config.webhookUrl)
        const requestUrl = webhookUrl
            ? `${webhookUrl}${webhookUrl.includes("?") ? "&" : "?"}diagnostic=simulate`
            : null

        if (!webhookUrl) {
            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: false,
                message: "Aucune URL de webhook publique n'est configurée pour ce compte SumUp",
                testedAt: new Date().toISOString(),
                webhookUrl: null,
                httpStatus: null,
                responseTimeMs: null,
                dryRun: true,
                eventType: null,
                providerCheckoutId: null,
                webhookStatus: null,
                webhookTransactionId: null,
                matchedAttemptId: null,
                matchedOrderId: null,
                raw: null,
            }
        }

        const simulatedPayload = buildSimulatedSumUpWebhookPayload(account, resolved.config.merchantCode)

        const startedAt = Date.now()

        try {
            const response = await postJson(requestUrl!, simulatedPayload)
            const responseTimeMs = Date.now() - startedAt
            const body = toRecord(response.body)

            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: response.ok,
                message: typeof body?.message === "string" && body.message.trim().length > 0
                    ? body.message.trim()
                    : (response.ok ? "Webhook dry-run accepté" : `Simulation webhook en erreur (${response.status})`),
                testedAt: new Date().toISOString(),
                webhookUrl,
                httpStatus: response.status,
                responseTimeMs,
                dryRun: body?.dryRun === true,
                eventType: typeof body?.eventType === "string" ? body.eventType : simulatedPayload.event_type,
                providerCheckoutId: typeof body?.providerCheckoutId === "string" ? body.providerCheckoutId : simulatedPayload.payload.client_transaction_id,
                webhookStatus: typeof body?.webhookStatus === "string" ? body.webhookStatus : simulatedPayload.payload.status,
                webhookTransactionId: typeof body?.webhookTransactionId === "string" ? body.webhookTransactionId : simulatedPayload.payload.transaction_id,
                matchedAttemptId: typeof body?.matchedAttemptId === "string" ? body.matchedAttemptId : null,
                matchedOrderId: typeof body?.matchedOrderId === "string" ? body.matchedOrderId : null,
                raw: buildDiagnosticRaw({
                    includeRaw: options?.includeRaw,
                    request: {
                        method: "POST",
                        url: requestUrl!,
                        headers: {
                            "Content-Type": "application/json",
                            "User-Agent": "TTM-Admin-SumUp-Diagnostic",
                        },
                        body: simulatedPayload,
                    },
                    response: {
                        ok: response.ok,
                        status: response.status,
                        headers: response.headers,
                        body: response.body,
                    },
                    notes: ["Le payload est envoyé en dry-run vers le webhook public."],
                }),
            }
        } catch (error) {
            return {
                accountId: account.id ?? "",
                provider: PaymentProvider.SUMUP,
                ok: false,
                message: error instanceof Error && error.message.trim().length > 0
                    ? error.message.trim()
                    : "Impossible de simuler le webhook SumUp",
                testedAt: new Date().toISOString(),
                webhookUrl,
                httpStatus: null,
                responseTimeMs: Date.now() - startedAt,
                dryRun: true,
                eventType: simulatedPayload.event_type,
                providerCheckoutId: simulatedPayload.payload.client_transaction_id,
                webhookStatus: simulatedPayload.payload.status,
                webhookTransactionId: simulatedPayload.payload.transaction_id,
                matchedAttemptId: null,
                matchedOrderId: null,
                raw: buildDiagnosticRaw({
                    includeRaw: options?.includeRaw,
                    request: {
                        method: "POST",
                        url: requestUrl!,
                        headers: {
                            "Content-Type": "application/json",
                            "User-Agent": "TTM-Admin-SumUp-Diagnostic",
                        },
                        body: simulatedPayload,
                    },
                    response: {
                        ok: null,
                        status: null,
                        body: {
                            error: error instanceof Error ? error.message : "Impossible de simuler le webhook SumUp",
                        },
                    },
                    notes: ["Le payload est envoyé en dry-run vers le webhook public."],
                }),
            }
        }
    },

    async runTerminalDiagnostic(args: {
        account: ProviderAccountRecordLike
        device: ProviderDeviceRecordLike
        pendingAttempt?: ProviderPendingAttemptLike | null
        options?: ProviderDiagnosticOptions
    }): Promise<PaymentProviderTerminalDiagnosticDto> {
        const resolved = this.resolveSumUpAccount(args.account)
        const requestUrl = `${SUMUP_API_BASE}/v0.1/merchants/${encodeURIComponent(resolved.config.merchantCode)}/readers/${encodeURIComponent(args.device.externalId)}/status`
        const requestHeaders = {
            Authorization: `Bearer ${resolved.secrets.apiSecretKey}`,
        }

        if (!args.device.active) {
            return {
                deviceId: args.device.id,
                accountId: args.account.id ?? "",
                provider: PaymentProvider.SUMUP,
                externalId: args.device.externalId,
                label: args.device.label,
                active: args.device.active,
                ok: false,
                connected: false,
                status: "offline",
                message: "Terminal inactif dans TTM",
                testedAt: new Date().toISOString(),
                providerDeviceStatus: null,
                providerScreenState: null,
                pendingAttemptId: args.pendingAttempt?.id ?? null,
                pendingOrderId: args.pendingAttempt?.orderId ?? null,
                raw: buildDiagnosticRaw({
                    includeRaw: args.options?.includeRaw,
                    notes: ["Aucune requête distante envoyée car le terminal est inactif dans TTM."],
                }),
            }
        }

        const remoteResponse = await fetchSumUpJson(requestUrl, resolved.secrets.apiSecretKey).catch((error: unknown) => ({
            ok: null,
            status: null,
            body: {
                error: error instanceof Error ? error.message : "Impossible de vérifier le statut du terminal",
            },
            headers: {},
        }))
        const remote = interpretSumUpReaderStatusResponse(remoteResponse, args.device.externalId)

        const effectiveStatus = args.pendingAttempt ? "busy" : remote.status
        const connected = args.pendingAttempt ? true : remote.status !== "offline" && remote.status !== "unknown"

        return {
            deviceId: args.device.id,
            accountId: args.account.id ?? "",
            provider: PaymentProvider.SUMUP,
            externalId: args.device.externalId,
            label: args.device.label,
            active: args.device.active,
            ok: effectiveStatus === "ready",
            connected,
            status: effectiveStatus,
            message: buildTerminalDiagnosticMessage(effectiveStatus, remote.reason, args.pendingAttempt),
            testedAt: new Date().toISOString(),
            providerDeviceStatus: remote.providerDeviceStatus,
            providerScreenState: remote.providerScreenState,
            pendingAttemptId: args.pendingAttempt?.id ?? null,
            pendingOrderId: args.pendingAttempt?.orderId ?? null,
            raw: buildDiagnosticRaw({
                includeRaw: args.options?.includeRaw,
                request: {
                    method: "GET",
                    url: requestUrl,
                    headers: requestHeaders,
                },
                response: {
                    ok: remoteResponse.ok,
                    status: remoteResponse.status,
                    headers: remoteResponse.headers,
                    body: remoteResponse.body,
                },
                notes: args.pendingAttempt
                    ? [
                        "Une tentative locale PENDING force l'état final à OCCUPÉ même si le provider répond autrement.",
                        "L'en-tête Authorization est masqué dans cette vue.",
                    ]
                    : ["L'en-tête Authorization est masqué dans cette vue."],
            }),
        }
    },

    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; providerDeviceStatus?: string | null; providerScreenState?: string | null }> {
    return fetchSumUpReaderStatusDetails(merchantCode, readerExternalId, apiSecretKey)
}

