import type { Schema } from '@platform-modules/db'
import { InvalidKeyComponentError } from '../errors.js'
import { confirmSettlement, type LedgerDbBag, type ProviderEvent } from '../index.js'
import type { SubscriptionProvider } from './port.js'
import type { ParsedSubscriptionWebhook } from './types.js'

const MAX_AMOUNT_MINOR_DIGITS = 21

export interface SubscriptionWebhookProvider extends SubscriptionProvider {
  parseWebhook(rawEvent: unknown): Promise<ParsedSubscriptionWebhook>
}

export type IngestWebhookDeps<S extends Schema = Schema> = LedgerDbBag<S> & {
  provider: SubscriptionWebhookProvider
}

function validateKeyComponent(value: string, field: string): string {
  if (typeof value !== 'string' || value.trim() === '') {
    throw new InvalidKeyComponentError(
      `subscription chargeKey component "${field}" must be non-empty`,
      { field, value: String(value) },
    )
  }
  if (value.includes(':')) {
    throw new InvalidKeyComponentError(
      `subscription chargeKey component "${field}" must not contain ":"`,
      { field, value },
    )
  }
  return value
}

export function buildChargeKey(subscriptionId: string, period: string): string {
  validateKeyComponent(subscriptionId, 'subscriptionId')
  validateKeyComponent(period, 'period')
  return `${subscriptionId}:${period}`
}

function amountMinorToNumber(amountMinor: bigint): number {
  if (amountMinor.toString().replace('-', '').length > MAX_AMOUNT_MINOR_DIGITS) {
    throw new Error(`subscription settlement amountMinor has too many digits, got ${amountMinor}`)
  }
  if (amountMinor < 0n) {
    throw new Error(`subscription settlement amountMinor must be non-negative, got ${amountMinor}`)
  }
  if (amountMinor > BigInt(Number.MAX_SAFE_INTEGER)) {
    throw new Error(
      `subscription settlement amountMinor exceeds Number.MAX_SAFE_INTEGER, got ${amountMinor}`,
    )
  }
  return Number(amountMinor)
}

export async function ingestWebhook<S extends Schema = Schema>(
  deps: IngestWebhookDeps<S>,
  rawEvent: unknown,
): Promise<ProviderEvent> {
  const parsed = await deps.provider.parseWebhook(rawEvent)
  if (parsed.kind === 'other') {
    return {
      eventId: parsed.eventId ?? 'subscription:other',
      kind: 'other',
      raw: parsed.raw,
    }
  }

  const chargeKey = buildChargeKey(parsed.subscriptionId, parsed.period)
  const amount = amountMinorToNumber(parsed.amountMinor)
  await confirmSettlement(
    chargeKey,
    {
      amount,
      currency: parsed.currency,
      providerRef: parsed.subscriptionId,
    },
    deps,
  )

  return {
    eventId: parsed.eventId ?? chargeKey,
    kind: 'settlement',
    chargeKey,
    providerRef: parsed.subscriptionId,
    amount,
    currency: parsed.currency,
    subscriptionId: parsed.subscriptionId,
    period: parsed.period,
    amountMinor: parsed.amountMinor,
  }
}
