import { ConflictError, NotFoundError, ValidationError } from '../../http/errors'
import { prisma } from '../../lib/prisma'

type BillingPayment = {
  id: string
  reservationId: string
  amount: number
  currency: string
  status: string
  type: string
  paymentProvider: string
  paymentMethod: string | null
  reference?: string | null
  note?: string | null
  receivedAt?: Date | null
  paidAt: Date | null
  createdAt: Date
  recordedByEmployee?: { firstName: string; lastName: string; email: string } | null
}

type BillingReservation = {
  id: string
  invoiceNumber: string | null
  contractNumber: string | null
  status: string
  paymentStatus: string
  startDate: Date
  endDate: Date
  totalAmount: number
  depositAmount: number
  customer: { firstName: string; lastName: string; email: string }
  vehicle: { make: string; model: string; licensePlate: string }
  rentalPayments: BillingPayment[]
}

type ListQuery = {
  page: number
  pageSize: number
  search: string
  paymentStatus: 'ALL' | 'UNPAID' | 'PARTIAL' | 'PAID'
  outstandingOnly: boolean
}

type SummaryQuery = {
  search: string
}

type ManualPaymentInput = {
  amountMinor: number
  currency: 'MAD'
  type: 'CHARGE' | 'DEPOSIT'
  method: 'CASH' | 'CHECK' | 'BANK_TRANSFER' | 'CARD' | 'PAYPAL' | 'OTHER'
  receivedAt?: string
  reference?: string
  note?: string
  idempotencyKey: string
}

const BILLING_CURRENCY = 'MAD'
const COLLECTED_STATUS = new Set(['SUCCEEDED'])

function sumCollected(payments: BillingPayment[], type: 'CHARGE' | 'DEPOSIT') {
  return payments.reduce((total, payment) => {
    if (payment.type !== type || !COLLECTED_STATUS.has(payment.status)) return total
    return total + payment.amount
  }, 0)
}

function derivePaymentStatus(invoiceBalanceDue: number, invoicePaid: number) {
  if (invoiceBalanceDue <= 0) return 'PAID'
  if (invoicePaid > 0) return 'PARTIAL'
  return 'UNPAID'
}

function deriveDepositStatus(depositRequired: number, depositCollected: number) {
  if (depositRequired <= 0) return 'NOT_REQUIRED'
  if (depositCollected <= 0) return 'OUTSTANDING'
  if (depositCollected < depositRequired) return 'PARTIALLY_COLLECTED'
  return 'HELD'
}

export function buildBillingInvoice(reservation: BillingReservation) {
  const invoiceTotal = reservation.totalAmount
  const invoicePaid = sumCollected(reservation.rentalPayments, 'CHARGE')
  const invoiceRefunded = 0
  const invoiceBalanceDue = Math.max(invoiceTotal - invoicePaid, 0)
  const depositRequired = reservation.depositAmount
  const depositCollected = sumCollected(reservation.rentalPayments, 'DEPOSIT')
  const depositRefunded = 0
  const depositHeld = Math.max(depositCollected - depositRefunded, 0)
  const depositOutstanding = Math.max(depositRequired - depositCollected, 0)

  const payments = [...reservation.rentalPayments]
    .sort((a, b) => new Date(b.paidAt ?? b.receivedAt ?? b.createdAt).getTime() - new Date(a.paidAt ?? a.receivedAt ?? a.createdAt).getTime())
    .map((payment) => ({
      id: payment.id,
      reservationId: payment.reservationId,
      amountMinor: payment.amount,
      currency: payment.currency,
      type: payment.type,
      channel: payment.paymentProvider === 'MANUAL' ? 'OFFLINE' : 'ONLINE',
      provider: payment.paymentProvider,
      method: payment.paymentMethod,
      status: payment.status,
      reference: payment.reference ?? null,
      note: payment.note ?? null,
      receivedAt: payment.receivedAt?.toISOString() ?? payment.paidAt?.toISOString() ?? payment.createdAt.toISOString(),
      paidAt: payment.paidAt?.toISOString() ?? null,
      createdAt: payment.createdAt.toISOString(),
      recordedBy: payment.recordedByEmployee
        ? {
            name: `${payment.recordedByEmployee.firstName} ${payment.recordedByEmployee.lastName}`,
            email: payment.recordedByEmployee.email,
          }
        : null,
      refundedAmountMinor: 0,
    }))

  return {
    id: reservation.id,
    reservationId: reservation.id,
    invoiceNumber: reservation.invoiceNumber,
    contractNumber: reservation.contractNumber,
    customer: reservation.customer,
    vehicle: reservation.vehicle,
    rentalPeriod: {
      startDate: reservation.startDate.toISOString(),
      endDate: reservation.endDate.toISOString(),
    },
    currency: BILLING_CURRENCY,
    status: reservation.status,
    paymentStatus: derivePaymentStatus(invoiceBalanceDue, invoicePaid),
    issuedAt: null,
    dueAt: reservation.startDate.toISOString(),
    subtotal: invoiceTotal,
    taxTotal: 0,
    discountTotal: 0,
    adjustmentTotal: 0,
    invoiceTotal,
    invoicePaid,
    invoiceRefunded,
    invoiceBalanceDue,
    depositRequired,
    depositCollected,
    depositRefunded,
    depositHeld,
    depositOutstanding,
    depositStatus: deriveDepositStatus(depositRequired, depositCollected),
    paymentCount: payments.length,
    latestPayment: payments[0] ?? null,
    payments,
  }
}

function buildReservationWhere(companyId: string, search = '') {
  const trimmedSearch = search.trim()
  const where: any = { companyId }

  if (trimmedSearch) {
    where.OR = [
      { invoiceNumber: { contains: trimmedSearch, mode: 'insensitive' } },
      { contractNumber: { contains: trimmedSearch, mode: 'insensitive' } },
      { customer: { firstName: { contains: trimmedSearch, mode: 'insensitive' } } },
      { customer: { lastName: { contains: trimmedSearch, mode: 'insensitive' } } },
      { customer: { email: { contains: trimmedSearch, mode: 'insensitive' } } },
      { vehicle: { make: { contains: trimmedSearch, mode: 'insensitive' } } },
      { vehicle: { model: { contains: trimmedSearch, mode: 'insensitive' } } },
      { vehicle: { licensePlate: { contains: trimmedSearch, mode: 'insensitive' } } },
    ]
  }

  return where
}

const reservationInclude = {
  customer: true,
  vehicle: true,
  rentalPayments: {
    include: {
      recordedByEmployee: {
        select: { firstName: true, lastName: true, email: true },
      },
    },
    orderBy: { createdAt: 'desc' },
  },
} as const

export async function listInvoices(companyId: string, query: ListQuery) {
  const where = buildReservationWhere(companyId, query.search)
  const reservations = await prisma.reservation.findMany({
    where,
    include: reservationInclude as any,
    orderBy: { createdAt: 'desc' },
  })

  let items = reservations.map((reservation) => buildBillingInvoice(reservation as any))
  if (query.paymentStatus !== 'ALL') {
    items = items.filter((invoice) => invoice.paymentStatus === query.paymentStatus)
  }
  if (query.outstandingOnly) {
    items = items.filter((invoice) => invoice.invoiceBalanceDue > 0 || invoice.depositOutstanding > 0)
  }

  const totalRecords = items.length
  const pagedItems = items.slice((query.page - 1) * query.pageSize, query.page * query.pageSize)

  return {
    items: pagedItems,
    page: query.page,
    pageSize: query.pageSize,
    totalItems: totalRecords,
    totalPages: Math.max(Math.ceil(totalRecords / query.pageSize), 1),
  }
}

export async function getSummary(companyId: string, query: SummaryQuery) {
  const reservations = await prisma.reservation.findMany({
    where: buildReservationWhere(companyId, query.search),
    include: reservationInclude as any,
  })

  const totals = reservations.reduce(
    (acc, reservation) => {
      const invoice = buildBillingInvoice(reservation as any)
      acc.totalInvoiced += invoice.invoiceTotal
      acc.totalCollected += invoice.invoicePaid
      acc.totalRefunded += invoice.invoiceRefunded
      acc.totalOutstanding += invoice.invoiceBalanceDue
      acc.depositsHeld += invoice.depositHeld
      if (invoice.invoiceBalanceDue > 0) acc.openInvoiceCount += 1
      return acc
    },
    {
      currency: BILLING_CURRENCY,
      totalInvoiced: 0,
      totalCollected: 0,
      totalRefunded: 0,
      totalOutstanding: 0,
      depositsHeld: 0,
      openInvoiceCount: 0,
      overdueInvoiceCount: 0,
    },
  )

  return totals
}

export async function getInvoice(companyId: string, invoiceId: string) {
  const reservation = await prisma.reservation.findFirst({
    where: { id: invoiceId, companyId },
    include: reservationInclude as any,
  })

  if (!reservation) throw new NotFoundError('Billing invoice not found')
  return buildBillingInvoice(reservation as any)
}

export async function recordManualPayment(companyId: string, employeeId: string, invoiceId: string, body: ManualPaymentInput) {
  const result = await prisma.$transaction(async (tx: any) => {
    const existingPayment = await tx.rentalPayment.findFirst({
      where: { companyId, idempotencyKey: body.idempotencyKey },
    })

    if (existingPayment) {
      return existingPayment
    }

    const reservation = await tx.reservation.findFirst({
      where: { id: invoiceId, companyId },
      include: reservationInclude as any,
    })

    if (!reservation) throw new NotFoundError('Billing invoice not found')

    const invoice = buildBillingInvoice(reservation)
    if (body.currency !== invoice.currency) {
      throw new ValidationError('Payment currency must match the invoice currency')
    }

    const permittedAmount = body.type === 'DEPOSIT' ? invoice.depositOutstanding : invoice.invoiceBalanceDue
    if (permittedAmount <= 0) {
      throw new ConflictError(body.type === 'DEPOSIT' ? 'Security deposit is already fully collected' : 'Invoice is already fully paid')
    }
    if (body.amountMinor > permittedAmount) {
      throw new ValidationError(body.type === 'DEPOSIT' ? 'Payment amount exceeds deposit outstanding' : 'Payment amount exceeds invoice balance due')
    }

    const receivedAt = body.receivedAt ? new Date(body.receivedAt) : new Date()
    const payment = await tx.rentalPayment.create({
      data: {
        companyId,
        reservationId: reservation.id,
        amount: body.amountMinor,
        currency: body.currency,
        status: 'SUCCEEDED',
        type: body.type,
        paymentProvider: 'MANUAL',
        paymentMethod: body.method,
        reference: body.reference,
        note: body.note,
        receivedAt,
        paidAt: receivedAt,
        recordedByEmployeeId: employeeId,
        idempotencyKey: body.idempotencyKey,
      },
    })

    if (body.type === 'CHARGE') {
      const paidAmount = invoice.invoicePaid + body.amountMinor
      await tx.reservation.update({
        where: { id: reservation.id },
        data: {
          paidAmount,
          paymentStatus: paidAmount >= invoice.invoiceTotal ? 'PAID' : 'PARTIAL',
        },
      })
    }

    return payment
  })

  return result
}
