import { ConflictError, ValidationError } from '../../http/errors'
import * as amanpay from '../../services/amanpayService'
import * as paypal from '../../services/paypalService'
import * as repo from './payment.repo'
import { getWebhookEventId, processWebhookOnce } from '../../security/webhookIdempotency'

export function listByCompany(companyId: string) {
  return repo.findByCompany(companyId)
}

export function listByReservation(reservationId: string, companyId: string) {
  return repo.findByReservation(reservationId, companyId)
}

function sumSucceededPayments(payments: any[], type: 'CHARGE' | 'DEPOSIT') {
  return payments.reduce((total: number, payment: any) => {
    if (payment.type !== type || payment.status !== 'SUCCEEDED') return total
    return total + payment.amount
  }, 0)
}

function getInvoicePaid(reservation: any, rentalPayments: any[]) {
  return Math.max(sumSucceededPayments(rentalPayments, 'CHARGE'), reservation.paidAmount ?? 0)
}

async function applyAmanpayWebhook(event: any) {
  const transactionId = event.transaction_id ?? event.id
  const status        = event.status?.toUpperCase()
  if (status === 'PAID' || status === 'SUCCEEDED') {
    const payment = await repo.findByAmanpay(transactionId)
    if (payment && payment.status !== 'SUCCEEDED') {
      await repo.markPaymentSucceeded(payment.id)
      if (payment.type === 'CHARGE') {
        await repo.incrementReservationPaid(payment.reservationId, payment.amount)
      }
    }
  } else if (status === 'FAILED') {
    await repo.markPaymentFailed({ amanpayTransactionId: transactionId })
  }
}

async function applyPaypalWebhook(event: any) {
  const eventType = event.event_type as string
  if (eventType === 'PAYMENT.CAPTURE.COMPLETED') {
    const captureId = event.resource?.id as string
    const payment   = await repo.findByPaypal(captureId)
    if (payment && payment.status !== 'SUCCEEDED') {
      await repo.markPaymentSucceeded(payment.id)
      if (payment.type === 'CHARGE') {
        await repo.incrementReservationPaid(payment.reservationId, payment.amount)
      }
    }
  } else if (eventType === 'PAYMENT.CAPTURE.DENIED') {
    await repo.markPaymentFailed({ paypalCaptureId: event.resource?.id })
  }
}

export async function handleAmanpayWebhook(event: any, rawBody: string | Buffer = JSON.stringify(event)) {
  return processWebhookOnce({
    provider: 'amanpay:payments',
    providerEventId: getWebhookEventId('amanpay', event),
    eventType: String(event.status ?? 'unknown'),
    rawBody,
    handle: () => applyAmanpayWebhook(event),
  })
}

export async function handlePaypalWebhook(event: any, rawBody: string | Buffer = JSON.stringify(event)) {
  return processWebhookOnce({
    provider: 'paypal:payments',
    providerEventId: getWebhookEventId('paypal', event),
    eventType: String(event.event_type ?? 'unknown'),
    rawBody,
    handle: () => applyPaypalWebhook(event),
  })
}

export async function initCharge(reservationId: string, companyId: string, body: {
  provider: 'AMANPAY' | 'PAYPAL'; type: 'CHARGE' | 'DEPOSIT'
  currency: 'MAD'; successUrl: string; failureUrl: string
}) {
  const reservation = await repo.findReservationOrThrow(reservationId, companyId)
  const rentalPayments = (reservation as any).rentalPayments ?? []
  const invoicePaid = getInvoicePaid(reservation, rentalPayments)
  const depositCollected = sumSucceededPayments(rentalPayments, 'DEPOSIT')
  const balanceDue = body.type === 'DEPOSIT'
    ? reservation.depositAmount - depositCollected
    : reservation.totalAmount - invoicePaid

  if (balanceDue <= 0) {
    throw new ConflictError(body.type === 'DEPOSIT' ? 'Security deposit is already fully collected' : 'Reservation is already fully paid')
  }

  const amount = balanceDue
  const description = `${body.type === 'DEPOSIT' ? 'Deposit' : 'Rental'}: ${reservation.vehicle.make} ${reservation.vehicle.model}`
  const orderId     = `${reservationId}-${body.type}-${Date.now()}`
  const webhookBase = process.env.API_URL ?? 'http://localhost:4000'

  let checkoutUrl: string
  let amanpayTransactionId: string | null = null
  let paypalCaptureId: string | null      = null

  if (body.provider === 'AMANPAY') {
    if (!amanpay.isConfigured()) throw new ValidationError('AmanPay is not configured')
    const result = await amanpay.createCheckout({
      amount, currency: body.currency, orderId, description,
      customerEmail: reservation.customer.email,
      customerName:  `${reservation.customer.firstName} ${reservation.customer.lastName}`,
      successUrl: body.successUrl, failureUrl: body.failureUrl,
      webhookUrl: `${webhookBase}/api/v1/payments/webhooks/amanpay`,
    })
    checkoutUrl          = result.checkoutUrl
    amanpayTransactionId = result.transactionId
  } else {
    if (!paypal.isConfigured()) throw new ValidationError('PayPal is not configured')
    const result = await paypal.createOrder({ amount, currency: body.currency, orderId, description, returnUrl: body.successUrl, cancelUrl: body.failureUrl })
    checkoutUrl     = result.approveUrl
    paypalCaptureId = result.orderId
  }

  const payment = await repo.createPayment({ companyId, reservationId, amount, currency: body.currency, status: 'PENDING', type: body.type, paymentProvider: body.provider, amanpayTransactionId, paypalCaptureId })
  return { payment, checkoutUrl }
}

export async function capturePaypal(reservationId: string, companyId: string, paypalOrderId: string) {
  const payment   = await repo.findByPaypalForCompany(paypalOrderId, companyId)
  const capture   = await paypal.captureOrder(paypalOrderId) as Record<string, any>
  const captureId = capture.purchase_units?.[0]?.payments?.captures?.[0]?.id ?? paypalOrderId
  const updated   = await repo.updatePaypalCapture(payment.id, captureId)
  if (payment.type === 'CHARGE') {
    await repo.incrementReservationPaid(payment.reservationId, payment.amount)
  }
  return updated
}

export async function recordManualPayment(reservationId: string, companyId: string, body: {
  amount: number; currency: string; type: string; paymentMethod: string
}) {
  const reservation   = await repo.findReservation(reservationId, companyId)
  const rentalPayments = (reservation as any).rentalPayments ?? []
  const invoicePaid = getInvoicePaid(reservation, rentalPayments)
  const depositCollected = sumSucceededPayments(rentalPayments, 'DEPOSIT')
  const remaining = body.type === 'DEPOSIT'
    ? reservation.depositAmount - depositCollected
    : reservation.totalAmount - invoicePaid

  if (remaining <= 0) {
    throw new ConflictError(body.type === 'DEPOSIT' ? 'Security deposit is already fully collected' : 'Reservation is already fully paid')
  }
  if (body.amount <= 0) {
    throw new ValidationError('Payment amount must be greater than zero')
  }
  if (body.amount > remaining) {
    throw new ValidationError(body.type === 'DEPOSIT' ? 'Payment amount exceeds deposit outstanding' : 'Payment amount exceeds remaining balance')
  }

  const payment      = await repo.createPayment({ companyId, reservationId, amount: body.amount, currency: body.currency, status: 'SUCCEEDED', type: body.type, paymentProvider: 'MANUAL', paymentMethod: body.paymentMethod, paidAt: new Date() })
  if (body.type === 'CHARGE') {
    const newPaidAmount = invoicePaid + body.amount
    await repo.setReservationPaidAmount(reservationId, newPaidAmount, newPaidAmount >= reservation.totalAmount ? 'PAID' : 'PARTIAL')
  }
  return payment
}

export async function refundPayment(reservationId: string, paymentId: string, companyId: string, amount?: number, reason?: string) {
  const payment = await repo.findPaymentOrThrow(paymentId, companyId, reservationId)
  if (payment.status !== 'SUCCEEDED') throw new ValidationError('Only succeeded payments can be refunded')
  if (!payment.amanpayTransactionId && !payment.paypalCaptureId) throw new ValidationError('Manual payments must be refunded outside the online gateway flow')

  const refundAmount = amount ?? payment.amount
  if (payment.paymentProvider === 'AMANPAY') {
    if (!payment.amanpayTransactionId) throw new Error('No AmanPay transaction ID')
    await amanpay.refundTransaction(payment.amanpayTransactionId, refundAmount, reason)
  } else {
    if (!payment.paypalCaptureId) throw new Error('No PayPal capture ID')
    await paypal.refundCapture(payment.paypalCaptureId, refundAmount, payment.currency, reason)
  }

  const isPartial = refundAmount < payment.amount
  const updated   = await repo.setPaymentRefunded(payment.id, isPartial)
  if (!isPartial) await repo.setReservationRefunded(payment.reservationId)
  return updated
}
