import { AppError } from "@/core/AppError"
import { PaymentProvider } from "@prisma/client"
import {
    PAYMENT_PROVIDER_DEFINITIONS,
    type PaymentProviderDefinitionDto,
    type PaymentProviderFieldDefinitionDto,
    type PaymentProviderInputMap,
    type PaymentProviderInputValue
} from "ttm-shared"
import { sumupPaymentProviderAdapter } from "./sumup-payment-provider.adapter"

export interface ProviderAccountRecordLike {
    provider: PaymentProvider
    config: unknown
    secrets: unknown
}

export interface ValidateProviderAccountPayloadArgs {
    config?: PaymentProviderInputMap | null
    secrets?: PaymentProviderInputMap | null
    existingConfig?: PaymentProviderInputMap | null
    existingSecrets?: PaymentProviderInputMap | null
}

export interface ValidatedProviderAccountPayload {
    config: PaymentProviderInputMap
    secrets: PaymentProviderInputMap
}

export interface ProviderPaymentSearchResult {
    items: unknown[]
    nextCursor?: string | null
}

export interface CheckPaymentStatusResult {
    status: "PENDING" | "PAID" | "FAILED" | "NOT_POLLABLE"
    providerTxId?: string | null
    failureReason?: string | null
}

export interface CancelAttemptResult {
    cancelled: boolean
    reason?: string | null
}

export interface PaymentProviderAdapter {
    provider: PaymentProvider
    definition: PaymentProviderDefinitionDto
    validateAccountPayload(args: ValidateProviderAccountPayloadArgs): ValidatedProviderAccountPayload
    readAccount(account: ProviderAccountRecordLike): ValidatedProviderAccountPayload
    getConfiguredSecretKeys(secrets: PaymentProviderInputMap): string[]
    listProviderPayments?: (args: Record<string, unknown>) => Promise<ProviderPaymentSearchResult>
    createRefund?: (args: Record<string, unknown>) => Promise<unknown>
    checkPaymentStatus?: (checkoutId: string, account: ProviderAccountRecordLike, configSnapshot?: Record<string, any> | null) => Promise<CheckPaymentStatusResult>
    cancelAttempt?: (checkoutId: string, account: ProviderAccountRecordLike, configSnapshot?: Record<string, any> | null) => Promise<CancelAttemptResult>
}

export function hasCheckPaymentStatus(
    adapter: PaymentProviderAdapter
): adapter is PaymentProviderAdapter & Required<Pick<PaymentProviderAdapter, "checkPaymentStatus">> {
    return typeof adapter.checkPaymentStatus === "function"
}

export function hasCancelAttempt(
    adapter: PaymentProviderAdapter
): adapter is PaymentProviderAdapter & Required<Pick<PaymentProviderAdapter, "cancelAttempt">> {
    return typeof adapter.cancelAttempt === "function"
}


export function hasProviderPaymentSearch(
    adapter: PaymentProviderAdapter
): adapter is PaymentProviderAdapter & Required<Pick<PaymentProviderAdapter, "listProviderPayments">> {
    return typeof adapter.listProviderPayments === "function"
}

export function hasProviderRefunds(
    adapter: PaymentProviderAdapter
): adapter is PaymentProviderAdapter & Required<Pick<PaymentProviderAdapter, "createRefund">> {
    return typeof adapter.createRefund === "function"
}

function isPlainObject(value: unknown): value is Record<string, unknown> {
    return typeof value === "object" && value !== null && !Array.isArray(value)
}

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 normalizeFieldValue(field: PaymentProviderFieldDefinitionDto, value: PaymentProviderInputValue | undefined): PaymentProviderInputValue {
    if (field.type === "boolean") {
        return typeof value === "boolean" ? value : false
    }

    if (field.type === "select") {
        return normalizeStringValue(value)
    }

    return normalizeStringValue(value)
}

function toInputMap(value: unknown): PaymentProviderInputMap {
    if (!isPlainObject(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 createDefinitionBackedAdapter(
    provider: PaymentProvider
): PaymentProviderAdapter {
    const definition = PAYMENT_PROVIDER_DEFINITIONS[provider]

    return {
        provider,
        definition,
        validateAccountPayload(args: ValidateProviderAccountPayloadArgs): ValidatedProviderAccountPayload {
            const baseConfig = { ...(args.existingConfig ?? {}), ...(args.config ?? {}) }
            const baseSecrets = { ...(args.existingSecrets ?? {}), ...(args.secrets ?? {}) }

            const normalizedConfig: PaymentProviderInputMap = {}
            const normalizedSecrets: PaymentProviderInputMap = {}

            for (const field of definition.fields) {
                const source = field.secret ? baseSecrets : baseConfig
                const normalized = normalizeFieldValue(field, source[field.key] as PaymentProviderInputValue | undefined)

                if (field.required) {
                    const isEmptyString = typeof normalized === "string" && normalized.trim().length === 0
                    const isMissing = normalized === null || normalized === undefined || isEmptyString
                    if (isMissing) {
                        throw new AppError(`Le champ \"${field.label}\" est requis pour ${definition.label}`, 400)
                    }
                }

                if (field.secret) {
                    normalizedSecrets[field.key] = normalized
                } else {
                    normalizedConfig[field.key] = normalized
                }
            }

            return {
                config: normalizedConfig,
                secrets: normalizedSecrets
            }
        },
        readAccount(account: ProviderAccountRecordLike): ValidatedProviderAccountPayload {
            return this.validateAccountPayload({
                config: toInputMap(account.config),
                secrets: toInputMap(account.secrets),
                existingConfig: {},
                existingSecrets: {}
            })
        },
        getConfiguredSecretKeys(secrets: PaymentProviderInputMap) {
            return definition.fields
                .filter((field: PaymentProviderFieldDefinitionDto) => field.secret)
                .map((field: PaymentProviderFieldDefinitionDto) => field.key)
                .filter((key: string) => {
                    const value = secrets[key]
                    return value !== null && value !== undefined && String(value).trim().length > 0
                })
        }
    }
}

const providerAdapters: Record<PaymentProvider, PaymentProviderAdapter> = {
    SUMUP: sumupPaymentProviderAdapter,
    STRIPE: createDefinitionBackedAdapter(PaymentProvider.STRIPE),
    SQUARE: createDefinitionBackedAdapter(PaymentProvider.SQUARE),
    ADYEN: createDefinitionBackedAdapter(PaymentProvider.ADYEN)
}

export function getPaymentProviderAdapter(provider: PaymentProvider): PaymentProviderAdapter {
    const adapter = providerAdapters[provider]
    if (!adapter) {
        throw new AppError(`Unsupported payment provider: ${provider}`, 400)
    }
    return adapter
}

export function listPaymentProviderDefinitions(): PaymentProviderDefinitionDto[] {
    return [sumupPaymentProviderAdapter].map((adapter) => adapter.definition)
}
