import { AbstractPaymentProvider } from "@medusajs/framework/utils"
import {
  InitiatePaymentInput,
  InitiatePaymentOutput,
  AuthorizePaymentInput,
  AuthorizePaymentOutput,
  CapturePaymentInput,
  CapturePaymentOutput,
  CancelPaymentInput,
  CancelPaymentOutput,
  DeletePaymentInput,
  DeletePaymentOutput,
  GetPaymentStatusInput,
  GetPaymentStatusOutput,
  RefundPaymentInput,
  RefundPaymentOutput,
  RetrievePaymentInput,
  RetrievePaymentOutput,
  UpdatePaymentInput,
  UpdatePaymentOutput,
  ProviderWebhookPayload,
  WebhookActionResult,
} from "@medusajs/framework/types"
import { MedusaError } from "@medusajs/framework/utils"

type InjectedDependencies = {
  logger: any
}

type Options = {
  apiKey: string
  webhookSecret: string
}

const XENDIT_API_BASE = "https://api.xendit.co"

class XenditProviderService extends AbstractPaymentProvider<Options> {
  static identifier = "xendit"

  protected logger_: any
  protected options_: Options

  constructor(cradle: Record<string, unknown>, options: Options) {
    super(cradle, options)
    this.logger_ = (cradle as any).logger
    this.options_ = options
  }

  private getAuthHeaders(): Record<string, string> {
    const encoded = Buffer.from(`${this.options_.apiKey}:`).toString("base64")
    return {
      Authorization: `Basic ${encoded}`,
      "Content-Type": "application/json",
    }
  }

  async initiatePayment(input: InitiatePaymentInput): Promise<InitiatePaymentOutput> {
    const { amount, currency_code, data, context } = input
    const amountInCents = Math.round(Number(amount))

    const body: Record<string, any> = {
      external_id: `order_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
      amount: amountInCents,
      currency: (currency_code || "IDR").toUpperCase(),
      description: data?.description || `Order payment`,
      invoice_duration: 86400,
      reminder_time: 1,
      customer: context?.customer
        ? {
            given_names: context.customer.first_name || "Customer",
            ...(context.customer.last_name ? { surname: context.customer.last_name } : {}),
            ...(context.customer.email ? { email: context.customer.email } : {}),
            ...(context.customer.phone ? { mobile_number: context.customer.phone } : {}),
          }
        : undefined,
      success_redirect_url: data?.success_url,
      failure_redirect_url: data?.failure_url,
      metadata: {
        session_id: data?.session_id,
      },
    }

    const response = await fetch(`${XENDIT_API_BASE}/v2/invoices`, {
      method: "POST",
      headers: this.getAuthHeaders(),
      body: JSON.stringify(body),
    })

    if (!response.ok) {
      const err = await response.text()
      this.logger_.error(`Xendit create invoice failed: ${err}`)
      throw new MedusaError(
        MedusaError.Types.INVALID_DATA,
        `Failed to create Xendit invoice: ${response.status}`
      )
    }

    const invoice = await response.json()

    return {
      id: invoice.id,
      data: {
        invoice_id: invoice.id,
        invoice_url: invoice.invoice_url,
        external_id: invoice.external_id,
      },
      status: "pending",
    }
  }

  async authorizePayment(input: AuthorizePaymentInput): Promise<AuthorizePaymentOutput> {
    const invoiceId = input.data?.invoice_id as string
    if (!invoiceId) {
      return { status: "error" }
    }

    const response = await fetch(`${XENDIT_API_BASE}/v2/invoices/${invoiceId}`, {
      method: "GET",
      headers: this.getAuthHeaders(),
    })

    if (!response.ok) {
      this.logger_.error(`Xendit get invoice failed: ${response.status}`)
      return { status: "error" }
    }

    const invoice = await response.json()

    switch (invoice.status) {
      case "PAID":
        return { status: "authorized", data: input.data }
      case "EXPIRED":
      case "FAILED":
        return { status: "canceled", data: input.data }
      default:
        return { status: "authorized", data: input.data }
    }
  }

  async capturePayment(input: CapturePaymentInput): Promise<CapturePaymentOutput> {
    return { data: input.data }
  }

  async cancelPayment(input: CancelPaymentInput): Promise<CancelPaymentOutput> {
    const invoiceId = input.data?.invoice_id as string
    if (!invoiceId) {
      return { data: input.data }
    }

    await fetch(`${XENDIT_API_BASE}/invoices/${invoiceId}/expire!`, {
      method: "POST",
      headers: this.getAuthHeaders(),
    }).catch((err) => {
      this.logger_.error(`Xendit expire invoice failed: ${err.message}`)
    })

    return { data: input.data }
  }

  async deletePayment(input: DeletePaymentInput): Promise<DeletePaymentOutput> {
    return this.cancelPayment(input)
  }

  async getPaymentStatus(input: GetPaymentStatusInput): Promise<GetPaymentStatusOutput> {
    const invoiceId = input.data?.invoice_id as string
    if (!invoiceId) {
      return { status: "error" }
    }

    const response = await fetch(`${XENDIT_API_BASE}/v2/invoices/${invoiceId}`, {
      method: "GET",
      headers: this.getAuthHeaders(),
    })

    if (!response.ok) {
      return { status: "error" }
    }

    const invoice = await response.json()

    const statusMap: Record<string, "authorized" | "captured" | "pending" | "requires_more" | "error" | "canceled"> = {
      PENDING: "pending",
      PAID: "authorized",
      SETTLED: "captured",
      EXPIRED: "canceled",
      FAILED: "error",
    }

    return {
      status: statusMap[invoice.status] || "pending",
      data: invoice,
    }
  }

  async refundPayment(input: RefundPaymentInput): Promise<RefundPaymentOutput> {
    const invoiceId = input.data?.invoice_id as string
    if (!invoiceId) {
      return { data: input.data }
    }

    const amount = Math.round(Number(input.amount))

    const response = await fetch(`${XENDIT_API_BASE}/refunds`, {
      method: "POST",
      headers: this.getAuthHeaders(),
      body: JSON.stringify({
        invoice_id: invoiceId,
        amount,
        external_id: `refund_${Date.now()}`,
      }),
    })

    if (!response.ok) {
      const err = await response.text()
      this.logger_.error(`Xendit refund failed: ${err}`)
      throw new MedusaError(
        MedusaError.Types.INVALID_DATA,
        "Failed to process Xendit refund"
      )
    }

    const refund = await response.json()
    return { data: { ...input.data, refund } }
  }

  async retrievePayment(input: RetrievePaymentInput): Promise<RetrievePaymentOutput> {
    const invoiceId = input.data?.invoice_id as string
    if (!invoiceId) {
      return { data: input.data }
    }

    const response = await fetch(`${XENDIT_API_BASE}/v2/invoices/${invoiceId}`, {
      method: "GET",
      headers: this.getAuthHeaders(),
    })

    if (!response.ok) {
      return { data: input.data }
    }

    const invoice = await response.json()
    return { data: invoice }
  }

  async updatePayment(input: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
    const invoiceId = input.data?.invoice_id as string

    if (invoiceId) {
      await fetch(`${XENDIT_API_BASE}/invoices/${invoiceId}/expire!`, {
        method: "POST",
        headers: this.getAuthHeaders(),
      }).catch(() => {})
    }

    const amountInCents = Math.round(Number(input.amount))
    const body: Record<string, any> = {
      external_id: `order_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
      amount: amountInCents,
      currency: (input.currency_code || "IDR").toUpperCase(),
      description: input.data?.description || "Updated order payment",
      invoice_duration: 86400,
      metadata: {
        session_id: input.data?.session_id,
      },
    }

    const response = await fetch(`${XENDIT_API_BASE}/v2/invoices`, {
      method: "POST",
      headers: this.getAuthHeaders(),
      body: JSON.stringify(body),
    })

    if (!response.ok) {
      return { status: "error", data: input.data }
    }

    const invoice = await response.json()

    return {
      data: {
        invoice_id: invoice.id,
        invoice_url: invoice.invoice_url,
      },
      status: "pending",
    }
  }

  async getWebhookActionAndData(
    payload: ProviderWebhookPayload["payload"]
  ): Promise<WebhookActionResult> {
    const data = payload.data as Record<string, any> | undefined
    if (!data) {
      return { action: "not_supported" }
    }

    const status = data.status as string

    switch (status) {
      case "PAID":
        return {
          action: "captured",
          data: {
            session_id: data.metadata?.session_id,
            amount: data.amount,
          },
        }
      case "EXPIRED":
      case "FAILED":
        return {
          action: "canceled",
          data: { session_id: data.metadata?.session_id, amount: data.amount ?? 0 },
        }
      default:
        return { action: "not_supported" }
    }
  }
}

export default XenditProviderService
