import {
  idempotencyKey,
  type ChargeRequest,
  type ChargeResult,
  type PaymentProvider,
  type ProviderEvent,
  RefundFailedError,
  type RefundRequest,
  type RefundReconciliationRequest,
  type RefundReconciliationResult,
  type RefundResult,
} from '../index.js'
import type { SubscriptionProvider } from '../subscriptions/index.js'
import type { CreateSubscriptionInput, Subscription } from '../subscriptions/index.js'
import { PaypalClient } from './client.js'
import {
  extractApproveUrl,
  extractSettledCapture,
  normalizeMinorUnits,
  parsePositivePaypalMoney,
  type PaypalCreds,
  PaypalPayloadError,
} from './types.js'
import { parsePaypalWebhook } from './webhook.js'

export class PaypalProvider implements PaymentProvider, SubscriptionProvider {
  readonly provider = 'paypal'
  readonly emitsInvoiceOnCharge = false
  private readonly client: PaypalClient

  constructor(private readonly creds: PaypalCreds) {
    this.client = new PaypalClient(creds)
  }

  async charge(req: ChargeRequest): Promise<ChargeResult> {
    const order = await this.client.createOrder(req)
    const orderId =
      typeof order.id === 'string' && order.id.trim() !== '' ? order.id : req.chargeKey
    const settledCapture = extractSettledCapture(order)
    if (settledCapture) {
      return {
        kind: 'settled',
        chargeKey: req.chargeKey,
        providerRef: settledCapture.captureId,
        amount: normalizeMinorUnits(settledCapture.amountMinor),
        currency: settledCapture.currency,
      }
    }

    const approvalUrl = extractApproveUrl(order)
    if (!approvalUrl) {
      throw new PaypalPayloadError('PayPal order requires payer approval but no approve link was returned')
    }

    return {
      kind: 'requires_client_action',
      chargeKey: req.chargeKey,
      providerRef: orderId,
      clientSecret: approvalUrl,
    }
  }

  async refund(req: RefundRequest): Promise<RefundResult> {
    const refund = await this.client.refundCapture(req)
    if (refund.status === 'PENDING') {
      return { kind: 'pending' }
    }

    const refundId =
      typeof refund.id === 'string' && refund.id.trim() !== '' ? refund.id : req.refundKey
    const { amountMinor, currency } = parsePositivePaypalMoney(refund.amount)
    const amount = normalizeMinorUnits(amountMinor)

    if (refund.status && refund.status !== 'COMPLETED') {
      throw new RefundFailedError({
        chargeKey: req.chargeKey,
        providerRef: refundId,
        status: refund.status,
      })
    }

    return {
      kind: 'refunded',
      refundKey: req.refundKey,
      chargeKey: req.chargeKey,
      providerRef: refundId,
      amount,
      currency,
    }
  }

  async reconcileRefund(
    req: RefundReconciliationRequest,
  ): Promise<RefundReconciliationResult> {
    if (req.amountMinor <= 0n || req.amountMinor > BigInt(Number.MAX_SAFE_INTEGER)) {
      return { kind: 'definite_failure', code: 'INVALID_AMOUNT' }
    }

    try {
      const refund = await this.client.refundCapture({
        refundKey: req.refundKey,
        chargeKey: req.providerChargeId,
        refundId: req.refundKey,
        providerChargeId: req.providerChargeId,
        amount: Number(req.amountMinor),
      })
      const { amountMinor, currency } = parsePositivePaypalMoney(refund.amount)
      if (refund.status === 'PENDING') {
        return { kind: 'pending_or_unknown' }
      }
      if (refund.status && refund.status !== 'COMPLETED') {
        return { kind: 'definite_failure', code: refund.status }
      }
      if (amountMinor !== req.amountMinor || currency !== req.currency) {
        return { kind: 'definite_failure', code: 'REFUND_FACT_MISMATCH' }
      }
      return {
        kind: 'confirmed',
        providerRefundId:
          typeof refund.id === 'string' && refund.id.trim() !== '' ? refund.id : req.refundKey,
        amountMinor,
        currency,
      }
    } catch {
      return { kind: 'pending_or_unknown' }
    }
  }

  async parseWebhook(raw: string, headers: Headers): Promise<ProviderEvent>
  async parseWebhook(headers: Headers, raw: string): Promise<ProviderEvent>
  async parseWebhook(
    rawOrHeaders: string | Headers,
    headersOrRaw: string | Headers,
  ): Promise<ProviderEvent> {
    const rawBody = typeof rawOrHeaders === 'string' ? rawOrHeaders : headersOrRaw
    const headers = rawOrHeaders instanceof Headers ? rawOrHeaders : headersOrRaw
    if (typeof rawBody !== 'string' || !(headers instanceof Headers)) {
      throw new PaypalPayloadError('PayPal parseWebhook requires a raw body string and Headers')
    }
    return parsePaypalWebhook(this.creds, rawBody, headers)
  }

  async createSubscription(input: CreateSubscriptionInput): Promise<Subscription> {
    return this.client.createSubscription(input)
  }

  async cancelSubscription(id: string): Promise<void> {
    await this.client.cancelSubscription(id)
  }

  async getSubscription(id: string): Promise<Subscription> {
    return this.client.getSubscription(id)
  }
}

export type {
  PaypalCapture,
  PaypalCreds,
  PaypalLink,
  PaypalMoney,
  PaypalOrder,
  PaypalRefund,
  PaypalSubscriptionRecord,
  PaypalWebhookEvent,
  PaypalWebhookHeaders,
} from './types.js'
export { PaypalApiError, PaypalPayloadError } from './types.js'
