import { AppError } from "@/core/AppError"
import { prisma } from "@/core/prisma"
import { PaymentAttemptStatus, PaymentProvider } from "@prisma/client"
import { Prisma } from "@prisma/client"
import type {
    CreatePaymentProviderAccountInput,
    CreatePaymentProviderDeviceInput,
    PaymentProviderAccountDto,
    PaymentProviderAccountEnvironment,
    PaymentProviderAccountMetadataDto,
    PaymentProviderAccountSummaryDto,
    PaymentProviderConnectionDiagnosticDto,
    PaymentProviderDeviceCatalogDto,
    PaymentProviderDeviceCatalogItemDto,
    PaymentProviderDeviceSyncState,
    PaymentProviderDeviceDto,
    PaymentProviderRemoteDeviceDto,
    PaymentProviderTerminalDiagnosticDto,
    PaymentProviderWebhookDiagnosticDto,
    PaymentProviderWebhookSimulationDiagnosticDto,
    RunPaymentProviderDiagnosticInput,
    UpdatePaymentProviderAccountInput,
    UpdatePaymentProviderDeviceInput
} from "ttm-shared"
import type { PaymentProviderInputMap } from "ttm-shared"
import {
    getPaymentProviderAdapter,
    hasConnectionDiagnostic,
    hasProviderAccountMetadata,
    hasProviderAccountSummary,
    hasRemoteDeviceDiscovery,
    hasTerminalDiagnostic,
    hasWebhookDiagnostic,
    hasWebhookSimulationDiagnostic,
} from "./providers/payment-provider.registry"

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 toProviderAccountDto(row: {
    id: string
    provider: PaymentProviderAccountDto["provider"]
    label: string | null
    config: Prisma.JsonValue | null
    secrets: Prisma.JsonValue | null
    metadata: Prisma.JsonValue | null
    active: boolean
    createdAt: Date
    updatedAt: Date
}): PaymentProviderAccountDto {
    const adapter = getPaymentProviderAdapter(row.provider)
    const resolved = adapter.readAccount({
        provider: row.provider as PaymentProvider,
        config: row.config,
        secrets: row.secrets,
        metadata: row.metadata,
    })
    const metadata = toProviderAccountMetadataDto(row.provider, row.metadata)
    const summary = hasProviderAccountSummary(adapter)
        ? adapter.getAccountSummary({
            account: {
                provider: row.provider as PaymentProvider,
                label: row.label,
                config: row.config,
                secrets: row.secrets,
                metadata: row.metadata,
            },
            metadata,
        })
        : buildFallbackProviderAccountSummary(row.label, row.config, metadata)

    return {
        id: row.id,
        provider: row.provider,
        label: row.label,
        config: resolved.config,
        configuredSecrets: adapter.getConfiguredSecretKeys(resolved.secrets),
        metadata,
        summary,
        active: row.active,
        createdAt: row.createdAt.toISOString(),
        updatedAt: row.updatedAt.toISOString()
    }
}

function toProviderAccountMetadataDto(
    provider: PaymentProvider,
    value: Prisma.JsonValue | null,
): PaymentProviderAccountMetadataDto | null {
    if (typeof value !== "object" || value === null || Array.isArray(value)) return null

    const record = value as Record<string, unknown>
    const readString = (key: string) => {
        const candidate = record[key]
        return typeof candidate === "string" && candidate.trim().length > 0 ? candidate.trim() : null
    }

    const refreshedAt = readString("refreshedAt")
    if (!refreshedAt) return null

    const rawEnvironment = readString("environment")
    const environment: PaymentProviderAccountEnvironment | null = rawEnvironment === "test" || rawEnvironment === "live"
        ? rawEnvironment
        : null

    return {
        provider,
        schemaVersion: 1,
        refreshedAt,
        environment,
        companyName: readString("companyName"),
        email: readString("email"),
        remoteMerchantCode: readString("remoteMerchantCode"),
        website: readString("website"),
        city: readString("city"),
        country: readString("country"),
    }
}

function buildFallbackProviderAccountSummary(
    label: string | null,
    config: Prisma.JsonValue | null,
    metadata: PaymentProviderAccountMetadataDto | null,
): PaymentProviderAccountSummaryDto | null {
    if (!label && !config && !metadata) return null

    const configuredMerchantCode = typeof toInputMap(config).merchantCode === "string"
        ? String(toInputMap(config).merchantCode).trim() || null
        : null
    const location = [metadata?.city, metadata?.country].filter((value): value is string => Boolean(value)).join(", ")
    const details = [
        metadata?.companyName && metadata.companyName !== label ? metadata.companyName : null,
        metadata?.email,
        metadata?.website,
        location,
    ].filter((value): value is string => Boolean(value))

    return {
        title: label ?? metadata?.companyName ?? null,
        subtitle: configuredMerchantCode ? `Merchant ${configuredMerchantCode}` : null,
        details,
    }
}

function toProviderDeviceDto(row: {
    id: string
    accountPaymentProviderId: string
    externalId: string
    label: string | null
    active: boolean
    accountPaymentProvider: {
        provider: PaymentProviderDeviceDto["provider"]
        label: string | null
    }
}): PaymentProviderDeviceDto {
    return {
        id: row.id,
        accountPaymentProviderId: row.accountPaymentProviderId,
        accountPaymentProviderLabel: row.accountPaymentProvider.label,
        provider: row.accountPaymentProvider.provider,
        externalId: row.externalId,
        label: row.label,
        active: row.active
    }
}

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

function normalizeExternalId(value: string) {
    return value.trim()
}

function toRemoteDiscoveryMessage(error: unknown) {
    if (error instanceof AppError) return error.message
    if (error instanceof Error && error.message.trim().length > 0) return error.message.trim()
    return "Impossible de récupérer les terminaux distants pour ce provider"
}

function toDeviceSyncState(configured: boolean, remoteDiscovered: boolean): PaymentProviderDeviceSyncState {
    if (configured && remoteDiscovered) return "synced"
    if (configured) return "local_only"
    return "remote_only"
}

function buildDeviceUsage(args: {
    defaultShopPaymentConfigs: number
    stationPaymentConfigs: number
    payments: number
    paymentAttempts: number
}) {
    const hasReferences = args.defaultShopPaymentConfigs > 0
        || args.stationPaymentConfigs > 0
        || args.payments > 0
        || args.paymentAttempts > 0

    return {
        usageCounts: {
            defaultShopPaymentConfigs: args.defaultShopPaymentConfigs,
            stationPaymentConfigs: args.stationPaymentConfigs,
            payments: args.payments,
            paymentAttempts: args.paymentAttempts,
        },
        canDelete: !hasReferences,
        deletionBlockedReason: hasReferences ? buildProviderDeviceDeletionBlockedMessage(args) : null,
    }
}

function upsertDeviceCatalogItem(
    itemsByExternalId: Map<string, PaymentProviderDeviceCatalogItemDto>,
    accountId: string,
    provider: PaymentProvider,
    remoteDevice: PaymentProviderRemoteDeviceDto,
) {
    const externalId = normalizeExternalId(remoteDevice.externalId)
    const existing = itemsByExternalId.get(externalId)

    if (existing) {
        existing.remoteLabel = remoteDevice.label
        existing.displayLabel = existing.label ?? remoteDevice.label ?? existing.externalId
        existing.remoteDiscovered = true
        existing.syncState = toDeviceSyncState(existing.configured, true)
        existing.model = remoteDevice.model
        existing.serialNumber = remoteDevice.serialNumber
        existing.remoteStatus = remoteDevice.status
        return
    }

    itemsByExternalId.set(externalId, {
        providerDeviceId: null,
        accountPaymentProviderId: accountId,
        provider,
        externalId,
        label: null,
        remoteLabel: remoteDevice.label,
        displayLabel: remoteDevice.label ?? externalId,
        active: null,
        configured: false,
        remoteDiscovered: true,
        syncState: "remote_only",
        model: remoteDevice.model,
        serialNumber: remoteDevice.serialNumber,
        remoteStatus: remoteDevice.status,
        usageCounts: {
            defaultShopPaymentConfigs: 0,
            stationPaymentConfigs: 0,
            payments: 0,
            paymentAttempts: 0,
        },
        canDelete: false,
        deletionBlockedReason: null,
    })
}

function buildProviderDeviceDeletionBlockedMessage(counts: {
    defaultShopPaymentConfigs: number
    stationPaymentConfigs: number
    payments: number
    paymentAttempts: number
}) {
    const parts = [
        counts.defaultShopPaymentConfigs > 0
            ? `${counts.defaultShopPaymentConfigs} configuration(s) de paiement shop`
            : null,
        counts.stationPaymentConfigs > 0
            ? `${counts.stationPaymentConfigs} configuration(s) de station`
            : null,
        counts.payments > 0
            ? `${counts.payments} paiement(s)`
            : null,
        counts.paymentAttempts > 0
            ? `${counts.paymentAttempts} tentative(s) de paiement`
            : null,
    ].filter((value): value is string => Boolean(value))

    if (!parts.length) {
        return "Impossible de supprimer ce terminal car il est encore référencé."
    }

    return `Impossible de supprimer ce terminal car il est encore utilisé par ${parts.join(", ")}. Si tu veux seulement vérifier la synchro provider, utilise plutôt \"Rafraîchir la liste distante\".`
}

async function ensureProviderAccountOwnedBy(accountId: string, ownerId: string) {
    const account = await prisma.accountPaymentProvider.findFirst({
        where: { id: accountId, ownerId }
    })

    if (!account) {
        throw new AppError("Provider account not found", 404)
    }

    return account
}

async function tryRefreshProviderAccountMetadata(ownerId: string, accountId: string) {
    try {
        return await paymentProviderAdminService.refreshProviderAccountMetadata(ownerId, accountId)
    } catch (error) {
        console.warn(`[paymentProviderAdminService] Unable to refresh provider metadata for account ${accountId}:`, error)
        return null
    }
}

async function ensureProviderDeviceOwnedBy(deviceId: string, ownerId: string) {
    const device = await prisma.providerDevice.findFirst({
        where: {
            id: deviceId,
            accountPaymentProvider: { ownerId }
        },
        include: {
            accountPaymentProvider: true
        }
    })

    if (!device) {
        throw new AppError("Provider device not found", 404)
    }

    return device
}

export const paymentProviderAdminService = {
    async listProviderAccounts(ownerId: string): Promise<PaymentProviderAccountDto[]> {
        const rows = await prisma.accountPaymentProvider.findMany({
            where: { ownerId },
            orderBy: [{ active: "desc" }, { provider: "asc" }, { label: "asc" }, { createdAt: "desc" }]
        })

        return rows.map((row) => toProviderAccountDto({
            ...row,
            config: row.config as Prisma.JsonValue | null,
            secrets: row.secrets as Prisma.JsonValue | null,
            metadata: row.metadata as Prisma.JsonValue | null,
        }))
    },

    async createProviderAccount(ownerId: string, input: CreatePaymentProviderAccountInput): Promise<PaymentProviderAccountDto> {
        const adapter = getPaymentProviderAdapter(input.provider as PaymentProvider)
        const validated = adapter.validateAccountPayload({
            config: input.config ?? {},
            secrets: input.secrets ?? {}
        })

        const created = await prisma.accountPaymentProvider.create({
            data: {
                ownerId,
                provider: input.provider as PaymentProvider,
                label: normalizeNullableString(input.label) ?? null,
                config: validated.config as Prisma.InputJsonValue,
                secrets: validated.secrets as Prisma.InputJsonValue,
                active: input.active
            }
        })

        const refreshed = await tryRefreshProviderAccountMetadata(ownerId, created.id)
        if (refreshed) {
            return refreshed
        }

        return toProviderAccountDto({
            ...created,
            config: created.config as Prisma.JsonValue | null,
            secrets: created.secrets as Prisma.JsonValue | null,
            metadata: created.metadata as Prisma.JsonValue | null,
        })
    },

    async updateProviderAccount(ownerId: string, accountId: string, input: UpdatePaymentProviderAccountInput): Promise<PaymentProviderAccountDto> {
        const existing = await ensureProviderAccountOwnedBy(accountId, ownerId)
        const provider = (input.provider ?? existing.provider) as PaymentProvider
        const adapter = getPaymentProviderAdapter(provider)

        const validated = adapter.validateAccountPayload({
            config: input.config ?? {},
            secrets: input.secrets ?? {},
            existingConfig: toInputMap(existing.config),
            existingSecrets: toInputMap(existing.secrets)
        })

        const updated = await prisma.accountPaymentProvider.update({
            where: { id: accountId },
            data: {
                ...(input.provider !== undefined ? { provider } : {}),
                ...(input.label !== undefined ? { label: normalizeNullableString(input.label) ?? null } : {}),
                ...(input.active !== undefined ? { active: input.active } : {}),
                config: validated.config as Prisma.InputJsonValue,
                secrets: validated.secrets as Prisma.InputJsonValue
            }
        })

        const shouldRefreshMetadata = input.provider !== undefined || input.config !== undefined || input.secrets !== undefined
        if (shouldRefreshMetadata) {
            const refreshed = await tryRefreshProviderAccountMetadata(ownerId, updated.id)
            if (refreshed) {
                return refreshed
            }
        }

        return toProviderAccountDto({
            ...updated,
            config: updated.config as Prisma.JsonValue | null,
            secrets: updated.secrets as Prisma.JsonValue | null,
            metadata: updated.metadata as Prisma.JsonValue | null,
        })
    },

    async refreshProviderAccountMetadata(ownerId: string, accountId: string): Promise<PaymentProviderAccountDto> {
        const account = await ensureProviderAccountOwnedBy(accountId, ownerId)
        const adapter = getPaymentProviderAdapter(account.provider)

        if (!hasProviderAccountMetadata(adapter)) {
            throw new AppError(`Metadata refresh is not supported for provider ${account.provider}`, 400)
        }

        const metadata = await adapter.getAccountMetadata({
            id: account.id,
            provider: account.provider,
            label: account.label,
            config: account.config,
            secrets: account.secrets,
            metadata: account.metadata,
        })

        const updated = await prisma.accountPaymentProvider.update({
            where: { id: accountId },
            data: {
                metadata: metadata === null ? Prisma.DbNull : metadata as unknown as Prisma.InputJsonValue,
            },
        })

        return toProviderAccountDto({
            ...updated,
            config: updated.config as Prisma.JsonValue | null,
            secrets: updated.secrets as Prisma.JsonValue | null,
            metadata: updated.metadata as Prisma.JsonValue | null,
        })
    },

    async deleteProviderAccount(ownerId: string, accountId: string): Promise<void> {
        const account = await prisma.accountPaymentProvider.findFirst({
            where: { id: accountId, ownerId },
            include: {
                providerDevices: { select: { id: true } },
                shopPaymentConfigs: { select: { id: true } },
                payments: { select: { id: true } },
                paymentAttempts: { select: { id: true } }
            }
        })

        if (!account) {
            throw new AppError("Provider account not found", 404)
        }

        if (account.providerDevices.length > 0) {
            throw new AppError("Cannot delete a provider account that still has devices", 400)
        }

        if (account.shopPaymentConfigs.length > 0 || account.payments.length > 0 || account.paymentAttempts.length > 0) {
            throw new AppError("Cannot delete a provider account that is still used by payment configurations or payments", 400)
        }

        await prisma.accountPaymentProvider.delete({
            where: { id: accountId }
        })
    },

    async runProviderConnectionDiagnostic(
        ownerId: string,
        accountId: string,
        options?: RunPaymentProviderDiagnosticInput,
    ): Promise<PaymentProviderConnectionDiagnosticDto> {
        const account = await ensureProviderAccountOwnedBy(accountId, ownerId)
        const adapter = getPaymentProviderAdapter(account.provider)

        if (!hasConnectionDiagnostic(adapter)) {
            throw new AppError(`Connection diagnostic is not supported for provider ${account.provider}`, 400)
        }

        return adapter.runConnectionDiagnostic({
            id: account.id,
            provider: account.provider,
            label: account.label,
            config: account.config,
            secrets: account.secrets,
        }, options)
    },

    async runProviderWebhookDiagnostic(
        ownerId: string,
        accountId: string,
        options?: RunPaymentProviderDiagnosticInput,
    ): Promise<PaymentProviderWebhookDiagnosticDto> {
        const account = await ensureProviderAccountOwnedBy(accountId, ownerId)
        const adapter = getPaymentProviderAdapter(account.provider)

        if (!hasWebhookDiagnostic(adapter)) {
            throw new AppError(`Webhook diagnostic is not supported for provider ${account.provider}`, 400)
        }

        return adapter.runWebhookDiagnostic({
            id: account.id,
            provider: account.provider,
            label: account.label,
            config: account.config,
            secrets: account.secrets,
        }, options)
    },

    async runProviderWebhookSimulationDiagnostic(
        ownerId: string,
        accountId: string,
        options?: RunPaymentProviderDiagnosticInput,
    ): Promise<PaymentProviderWebhookSimulationDiagnosticDto> {
        const account = await ensureProviderAccountOwnedBy(accountId, ownerId)
        const adapter = getPaymentProviderAdapter(account.provider)

        if (!hasWebhookSimulationDiagnostic(adapter)) {
            throw new AppError(`Advanced webhook diagnostic is not supported for provider ${account.provider}`, 400)
        }

        return adapter.runWebhookSimulationDiagnostic({
            id: account.id,
            provider: account.provider,
            label: account.label,
            config: account.config,
            secrets: account.secrets,
        }, options)
    },

    async listProviderDevices(ownerId: string): Promise<PaymentProviderDeviceDto[]> {
        const rows = await prisma.providerDevice.findMany({
            where: {
                accountPaymentProvider: { ownerId }
            },
            include: {
                accountPaymentProvider: {
                    select: {
                        provider: true,
                        label: true
                    }
                }
            },
            orderBy: [
                { active: "desc" },
                { accountPaymentProviderId: "asc" },
                { label: "asc" },
                { externalId: "asc" }
            ]
        })

        return rows.map(toProviderDeviceDto)
    },

    async listProviderDeviceCatalog(ownerId: string, accountId: string): Promise<PaymentProviderDeviceCatalogDto> {
        const account = await ensureProviderAccountOwnedBy(accountId, ownerId)
        const adapter = getPaymentProviderAdapter(account.provider)

        const localDevices = await prisma.providerDevice.findMany({
            where: { accountPaymentProviderId: account.id },
            include: {
                _count: {
                    select: {
                        defaultShopPaymentConfigs: true,
                        stationPaymentConfigs: true,
                        payments: true,
                        paymentAttempts: true,
                    }
                }
            },
            orderBy: [{ active: "desc" }, { label: "asc" }, { externalId: "asc" }],
        })

        const itemsByExternalId = new Map<string, PaymentProviderDeviceCatalogItemDto>()

        for (const device of localDevices) {
            const externalId = normalizeExternalId(device.externalId)
            const usage = buildDeviceUsage({
                defaultShopPaymentConfigs: device._count.defaultShopPaymentConfigs,
                stationPaymentConfigs: device._count.stationPaymentConfigs,
                payments: device._count.payments,
                paymentAttempts: device._count.paymentAttempts,
            })
            itemsByExternalId.set(externalId, {
                providerDeviceId: device.id,
                accountPaymentProviderId: account.id,
                provider: account.provider,
                externalId,
                label: device.label,
                remoteLabel: null,
                displayLabel: device.label ?? externalId,
                active: device.active,
                configured: true,
                remoteDiscovered: false,
                syncState: "local_only",
                model: null,
                serialNumber: null,
                remoteStatus: null,
                ...usage,
            })
        }

        const remoteDiscoverySupported = hasRemoteDeviceDiscovery(adapter)
        let remoteDiscoveryMessage: string | null = remoteDiscoverySupported
            ? null
            : `La découverte distante n'est pas disponible pour ${account.provider}`

        if (remoteDiscoverySupported) {
            try {
                const remoteDevices = await adapter.listRemoteDevices({
                    id: account.id,
                    provider: account.provider,
                    label: account.label,
                    config: account.config,
                    secrets: account.secrets,
                    metadata: account.metadata,
                })

                for (const remoteDevice of remoteDevices) {
                    upsertDeviceCatalogItem(itemsByExternalId, account.id, account.provider, remoteDevice)
                }
            } catch (error) {
                remoteDiscoveryMessage = toRemoteDiscoveryMessage(error)
            }
        }

        return {
            accountId: account.id,
            provider: account.provider,
            remoteDiscoverySupported,
            remoteDiscoveryMessage,
            fetchedAt: new Date().toISOString(),
            items: Array.from(itemsByExternalId.values()).sort((left, right) => {
                if (left.configured !== right.configured) return left.configured ? -1 : 1
                return left.displayLabel.localeCompare(right.displayLabel, "fr", { sensitivity: "base" })
            }),
        }
    },

    async createProviderDevice(ownerId: string, input: CreatePaymentProviderDeviceInput): Promise<PaymentProviderDeviceDto> {
        await ensureProviderAccountOwnedBy(input.accountPaymentProviderId, ownerId)

        try {
            const created = await prisma.providerDevice.create({
                data: {
                    accountPaymentProviderId: input.accountPaymentProviderId,
                    externalId: input.externalId.trim(),
                    label: normalizeNullableString(input.label) ?? null,
                    active: input.active
                },
                include: {
                    accountPaymentProvider: {
                        select: {
                            provider: true,
                            label: true
                        }
                    }
                }
            })

            return toProviderDeviceDto(created)
        } catch (error: unknown) {
            if (error instanceof Error && error.message.includes("Unique constraint")) {
                throw new AppError("A device with this external id already exists for the selected provider account", 400)
            }
            throw error
        }
    },

    async updateProviderDevice(ownerId: string, deviceId: string, input: UpdatePaymentProviderDeviceInput): Promise<PaymentProviderDeviceDto> {
        const existing = await prisma.providerDevice.findFirst({
            where: {
                id: deviceId,
                accountPaymentProvider: { ownerId }
            }
        })

        if (!existing) {
            throw new AppError("Provider device not found", 404)
        }

        const accountPaymentProviderId = input.accountPaymentProviderId ?? existing.accountPaymentProviderId
        await ensureProviderAccountOwnedBy(accountPaymentProviderId, ownerId)

        try {
            const updated = await prisma.providerDevice.update({
                where: { id: deviceId },
                data: {
                    ...(input.accountPaymentProviderId !== undefined ? { accountPaymentProviderId } : {}),
                    ...(input.externalId !== undefined ? { externalId: input.externalId.trim() } : {}),
                    ...(input.label !== undefined ? { label: normalizeNullableString(input.label) ?? null } : {}),
                    ...(input.active !== undefined ? { active: input.active } : {})
                },
                include: {
                    accountPaymentProvider: {
                        select: {
                            provider: true,
                            label: true
                        }
                    }
                }
            })

            return toProviderDeviceDto(updated)
        } catch (error: unknown) {
            if (error instanceof Error && error.message.includes("Unique constraint")) {
                throw new AppError("A device with this external id already exists for the selected provider account", 400)
            }
            throw error
        }
    },

    async deleteProviderDevice(ownerId: string, deviceId: string): Promise<void> {
        const device = await prisma.providerDevice.findFirst({
            where: {
                id: deviceId,
                accountPaymentProvider: { ownerId }
            },
            include: {
                defaultShopPaymentConfigs: { select: { id: true } },
                stationPaymentConfigs: { select: { id: true } },
                payments: { select: { id: true } },
                paymentAttempts: { select: { id: true } }
            }
        })

        if (!device) {
            throw new AppError("Provider device not found", 404)
        }

        if (
            device.defaultShopPaymentConfigs.length > 0 ||
            device.stationPaymentConfigs.length > 0 ||
            device.payments.length > 0 ||
            device.paymentAttempts.length > 0
        ) {
            throw new AppError(buildProviderDeviceDeletionBlockedMessage({
                defaultShopPaymentConfigs: device.defaultShopPaymentConfigs.length,
                stationPaymentConfigs: device.stationPaymentConfigs.length,
                payments: device.payments.length,
                paymentAttempts: device.paymentAttempts.length,
            }), 400)
        }

        await prisma.providerDevice.delete({
            where: { id: deviceId }
        })
    },

    async runProviderTerminalDiagnostic(
        ownerId: string,
        deviceId: string,
        options?: RunPaymentProviderDiagnosticInput,
    ): Promise<PaymentProviderTerminalDiagnosticDto> {
        const device = await ensureProviderDeviceOwnedBy(deviceId, ownerId)
        const adapter = getPaymentProviderAdapter(device.accountPaymentProvider.provider)

        if (!hasTerminalDiagnostic(adapter)) {
            throw new AppError(`Terminal diagnostic is not supported for provider ${device.accountPaymentProvider.provider}`, 400)
        }

        const pendingAttempt = await prisma.paymentAttempt.findFirst({
            where: {
                providerDeviceId: device.id,
                status: PaymentAttemptStatus.PENDING,
            },
            orderBy: { createdAt: "desc" },
            select: {
                id: true,
                orderId: true,
            }
        })

        return adapter.runTerminalDiagnostic({
            account: {
                id: device.accountPaymentProvider.id,
                provider: device.accountPaymentProvider.provider,
                label: device.accountPaymentProvider.label,
                config: device.accountPaymentProvider.config,
                secrets: device.accountPaymentProvider.secrets,
            },
            device: {
                id: device.id,
                externalId: device.externalId,
                label: device.label,
                active: device.active,
            },
            pendingAttempt,
            options,
        })
    }
}
