import { prisma } from '../../lib/prisma'
import { NotFoundError, ValidationError } from '../../http/errors'
import { generateInvoicePdf } from '../../services/invoicePdfService'

const BLOCKING_INVOICE_TYPES = new Set([
  'SUBSCRIPTION_INITIAL',
  'SUBSCRIPTION_RENEWAL',
  'TRIAL_CONVERSION',
  'SUBSCRIPTION_UPGRADE',
])

const EDITABLE_BILLING_STATUSES = new Set(['DRAFT', 'OPEN', 'PAYMENT_PENDING', 'PAST_DUE', 'PARTIALLY_PAID'])

function toSequenceNumber(value: unknown) {
  if (typeof value === 'bigint') return Number(value)
  if (typeof value === 'number') return value
  if (typeof value === 'string') return Number(value)
  throw new Error('Invalid invoice sequence value')
}

function buildSequentialInvoiceNumber(sequence: number, date: Date) {
  return `INV-${date.getFullYear()}-${String(sequence).padStart(6, '0')}`
}

function isBlockingInvoiceType(invoiceType: string) {
  return BLOCKING_INVOICE_TYPES.has(invoiceType)
}

function getInvoiceTermsDays(invoiceTerms: string, explicitNetTermsDays: number) {
  if (explicitNetTermsDays > 0) return explicitNetTermsDays
  const map: Record<string, number> = {
    DUE_ON_RECEIPT: 0,
    NET_7: 7,
    NET_15: 15,
    NET_30: 30,
    NET_45: 45,
    NET_60: 60,
  }
  return map[invoiceTerms] ?? 0
}

function addDays(date: Date, days: number) {
  const copy = new Date(date)
  copy.setDate(copy.getDate() + days)
  return copy
}

function normalizeAddress(address: any) {
  if (!address) return null
  if (typeof address === 'string') return { formatted: address }
  return address
}

function fmtLegacyInvoiceType(_invoice: any) {
  return 'SUBSCRIPTION_INITIAL'
}

function mapLegacyInvoiceStatus(status: string) {
  switch (status) {
    case 'PAID':
      return 'PAID'
    case 'FAILED':
      return 'PAST_DUE'
    case 'REFUNDED':
      return 'REFUNDED'
    case 'VOIDED':
      return 'VOID'
    default:
      return 'OPEN'
  }
}

function calculateLineAmounts(items: Array<{ type: string; amount: number }>) {
  const subtotalAmount = items
    .filter((item) => !['DISCOUNT', 'CREDIT', 'TAX'].includes(item.type))
    .reduce((sum, item) => sum + item.amount, 0)
  const discountAmount = Math.abs(
    items.filter((item) => item.type === 'DISCOUNT').reduce((sum, item) => sum + item.amount, 0),
  )
  const creditAmount = Math.abs(
    items.filter((item) => item.type === 'CREDIT').reduce((sum, item) => sum + item.amount, 0),
  )
  const taxAmount = items.filter((item) => item.type === 'TAX').reduce((sum, item) => sum + item.amount, 0)
  const totalAmount = Math.max(subtotalAmount - discountAmount - creditAmount + taxAmount, 0)
  return { subtotalAmount, discountAmount, creditAmount, taxAmount, totalAmount }
}

async function createBillingEvent(tx: any, data: {
  billingAccountId?: string | null
  invoiceId?: string | null
  subscriptionId?: string | null
  companyId?: string | null
  eventType: string
  source: string
  payload?: Record<string, unknown>
}) {
  await tx.billingEvent.create({
    data: {
      billingAccountId: data.billingAccountId ?? null,
      invoiceId: data.invoiceId ?? null,
      subscriptionId: data.subscriptionId ?? null,
      companyId: data.companyId ?? null,
      eventType: data.eventType,
      source: data.source,
      payload: data.payload ?? {},
      occurredAt: new Date(),
    },
  })
}

async function createAuditLog(data: {
  adminUserId: string
  action: string
  resource: string
  resourceId?: string
  companyId?: string
  before?: any
  after?: any
  note?: string
  ipAddress?: string
}) {
  await prisma.auditLog.create({ data: data as any })
}

async function ensurePrimaryBillingAccount(companyId: string, tx: any = prisma) {
  const existing = await tx.billingAccount.findFirst({
    where: { companyId, isPrimary: true },
    include: { company: { include: { contractSettings: true, subscription: true } }, creditBalances: true },
  })
  if (existing) return existing

  const company = await tx.company.findUniqueOrThrow({
    where: { id: companyId },
    include: { accountingSettings: true, contractSettings: true, subscription: true },
  })

  const created = await tx.billingAccount.create({
    data: {
      companyId,
      isPrimary: true,
      legalName: company.name,
      billingEmail: company.email,
      billingAddress: normalizeAddress(company.address),
      defaultCurrency: company.accountingSettings?.currency ?? 'MAD',
      invoiceTerms: 'DUE_ON_RECEIPT',
      netTermsDays: 0,
      metadata: {},
    },
    include: { company: { include: { contractSettings: true, subscription: true } }, creditBalances: true },
  })

  await tx.billingCreditBalance.create({
    data: {
      billingAccountId: created.id,
      currency: created.defaultCurrency,
      balanceAmount: 0,
    },
  })

  await createBillingEvent(tx, {
    billingAccountId: created.id,
    companyId,
    eventType: 'billing_account.created',
    source: 'system',
    payload: { companyId },
  })

  return tx.billingAccount.findUniqueOrThrow({
    where: { id: created.id },
    include: { company: { include: { contractSettings: true, subscription: true } }, creditBalances: true },
  })
}

async function getNextInvoiceSequence(tx: any) {
  const latest = await tx.billingInvoice.findFirst({
    where: { invoiceSequence: { not: null } },
    select: { invoiceSequence: true },
    orderBy: { invoiceSequence: 'desc' },
  })
  return (latest?.invoiceSequence ?? 0) + 1
}

async function maybeRestoreSubscription(tx: any, invoice: any) {
  if (!invoice.subscriptionId || !invoice.isSubscriptionBlocking) return
  const subscription = await tx.subscription.findUnique({ where: { id: invoice.subscriptionId } })
  if (!subscription) return
  if (['PAYMENT_PENDING', 'PAST_DUE', 'SUSPENDED', 'TRIALING', 'UNPAID'].includes(subscription.status)) {
    await tx.subscription.update({
      where: { id: subscription.id },
      data: {
        status: 'ACTIVE',
        paymentPendingSince: null,
        paymentDueAt: null,
        pastDueSince: null,
        suspendedAt: null,
        retryCount: 0,
      },
    })
  }
}

async function maybeCancelSubscriptionForWriteoff(tx: any, invoice: any) {
  if (!invoice.subscriptionId || !invoice.isSubscriptionBlocking) return
  const subscription = await tx.subscription.findUnique({ where: { id: invoice.subscriptionId } })
  if (!subscription || ['CANCELLED', 'EXPIRED'].includes(subscription.status)) return
  await tx.subscription.update({
    where: { id: subscription.id },
    data: {
      status: 'CANCELLED',
      cancelledAt: new Date(),
      endedAt: new Date(),
    },
  })
}

async function syncOneLegacyInvoice(tx: any, legacy: any) {
  const account = await ensurePrimaryBillingAccount(legacy.companyId, tx)
  const status = mapLegacyInvoiceStatus(legacy.status)
  const sequence = await getNextInvoiceSequence(tx)
  const invoiceDate = legacy.createdAt ?? new Date()
  const invoiceNumber = buildSequentialInvoiceNumber(sequence, invoiceDate)
  const amountPaid = ['PAID', 'REFUNDED'].includes(status) ? legacy.amount : 0
  const amountDue = ['OPEN', 'PAST_DUE', 'PAYMENT_PENDING'].includes(status) ? legacy.amount : 0

  const billingInvoice = await tx.billingInvoice.create({
    data: {
      billingAccountId: account.id,
      companyId: legacy.companyId,
      subscriptionId: legacy.subscriptionId,
      invoiceNumber,
      invoiceSequence: sequence,
      invoiceType: fmtLegacyInvoiceType(legacy),
      status,
      currency: legacy.currency,
      subtotalAmount: legacy.amount,
      totalAmount: legacy.amount,
      amountPaid,
      amountDue,
      invoiceDate,
      dueAt: legacy.dueAt ?? legacy.createdAt ?? new Date(),
      finalizedAt: legacy.createdAt ?? new Date(),
      paidAt: legacy.paidAt,
      voidedAt: legacy.voidedAt,
      billingName: account.legalName,
      billingEmail: account.billingEmail,
      billingAddress: account.billingAddress,
      paymentProvider: legacy.paymentProvider,
      isSubscriptionBlocking: true,
      metadata: { legacySubscriptionInvoiceId: legacy.id },
    },
  })

  await tx.billingInvoiceLineItem.create({
    data: {
      invoiceId: billingInvoice.id,
      subscriptionId: legacy.subscriptionId,
      plan: legacy.subscription?.plan ?? null,
      type: 'SUBSCRIPTION_FEE',
      description: `${legacy.subscription?.plan ?? 'Subscription'} legacy charge`,
      quantity: 1,
      unitAmount: legacy.amount,
      amount: legacy.amount,
      currency: legacy.currency,
      periodStart: legacy.subscription?.currentPeriodStart ?? null,
      periodEnd: legacy.subscription?.currentPeriodEnd ?? null,
      metadata: { legacySubscriptionInvoiceId: legacy.id },
    },
  })

  for (const attempt of legacy.attempts ?? []) {
    await tx.billingPaymentAttempt.create({
      data: {
        invoiceId: billingInvoice.id,
        billingAccountId: account.id,
        providerPaymentId: attempt.providerPaymentId,
        status: attempt.status === 'succeeded' ? 'SUCCEEDED' : attempt.status === 'failed' ? 'FAILED' : 'PENDING',
        amount: legacy.amount,
        currency: legacy.currency,
        failureCode: attempt.failureCode,
        failureMessage: attempt.failureMessage,
        attemptedAt: attempt.attemptedAt,
        metadata: { legacyPaymentAttemptId: attempt.id },
      },
    })
  }

  await tx.subscriptionInvoice.update({
    where: { id: legacy.id },
    data: { billingInvoiceId: billingInvoice.id },
  })

  await createBillingEvent(tx, {
    billingAccountId: account.id,
    invoiceId: billingInvoice.id,
    subscriptionId: legacy.subscriptionId,
    companyId: legacy.companyId,
    eventType: 'invoice.created',
    source: 'legacy_sync',
    payload: { legacySubscriptionInvoiceId: legacy.id, status },
  })
}

async function syncLegacySubscriptionInvoices(companyId?: string) {
  const unsynced = await prisma.subscriptionInvoice.findMany({
    where: {
      billingInvoiceId: null,
      ...(companyId ? { companyId } : {}),
    },
    include: {
      subscription: true,
      attempts: true,
    },
    orderBy: { createdAt: 'asc' },
    take: companyId ? 100 : 250,
  })

  for (const legacy of unsynced) {
    await prisma.$transaction(async (tx: any) => {
      const latest = await tx.subscriptionInvoice.findUnique({
        where: { id: legacy.id },
        include: { subscription: true, attempts: true },
      })
      if (!latest || latest.billingInvoiceId) return
      await syncOneLegacyInvoice(tx, latest)
    })
  }
}

function buildBillingAccountWhere(query: { q?: string; status?: string; plan?: string }) {
  const where: any = {}
  if (query.q) {
    where.OR = [
      { legalName: { contains: query.q, mode: 'insensitive' } },
      { billingEmail: { contains: query.q, mode: 'insensitive' } },
      { company: { name: { contains: query.q, mode: 'insensitive' } } },
      { company: { email: { contains: query.q, mode: 'insensitive' } } },
      { company: { slug: { contains: query.q, mode: 'insensitive' } } },
    ]
  }
  if (query.status || query.plan) {
    where.company = {
      subscription: {
        ...(query.status ? { status: query.status } : {}),
        ...(query.plan ? { plan: query.plan } : {}),
      },
    }
  }
  return where
}

export async function listBillingAccounts(query: { q?: string; status?: string; plan?: string; page: number; pageSize: number }) {
  await syncLegacySubscriptionInvoices()

  const matchingCompanies = await prisma.company.findMany({
    where: {
      ...(query.q
        ? {
            OR: [
              { name: { contains: query.q, mode: 'insensitive' } },
              { email: { contains: query.q, mode: 'insensitive' } },
              { slug: { contains: query.q, mode: 'insensitive' } },
            ],
          }
        : {}),
      ...(query.status || query.plan
        ? {
            subscription: {
              ...(query.status ? { status: query.status as any } : {}),
              ...(query.plan ? { plan: query.plan as any } : {}),
            },
          }
        : {}),
    },
    select: { id: true },
    take: 250,
  })

  for (const company of matchingCompanies) {
    await ensurePrimaryBillingAccount(company.id)
  }

  const where = buildBillingAccountWhere(query)
  const [accounts, total, invoiceAgg] = await Promise.all([
    prisma.billingAccount.findMany({
      where,
      include: {
        company: {
          select: {
            id: true,
            name: true,
            email: true,
            slug: true,
            status: true,
            subscription: {
              select: {
                id: true,
                plan: true,
                billingPeriod: true,
                status: true,
                currentPeriodEnd: true,
                cancelAtPeriodEnd: true,
              },
            },
          },
        },
        creditBalances: true,
        invoices: {
          orderBy: { createdAt: 'desc' },
          take: 5,
          select: {
            id: true,
            invoiceNumber: true,
            invoiceType: true,
            status: true,
            totalAmount: true,
            amountDue: true,
            amountPaid: true,
            currency: true,
            dueAt: true,
            paidAt: true,
            createdAt: true,
          },
        },
      },
      orderBy: { createdAt: 'desc' },
      skip: (query.page - 1) * query.pageSize,
      take: query.pageSize,
    }),
    prisma.billingAccount.count({ where }),
    prisma.billingInvoice.groupBy({
      by: ['status'],
      _sum: { amountDue: true, totalAmount: true },
      _count: { _all: true },
    }),
  ])

  const invoiceAggItems = invoiceAgg as any[]

  const stats = {
    billingAccountCount: total,
    openInvoiceCount: invoiceAggItems
      .filter((item) => ['OPEN', 'PAYMENT_PENDING', 'PAST_DUE', 'PARTIALLY_PAID'].includes(item.status))
      .reduce((sum, item) => sum + item._count._all, 0),
    pastDueInvoiceCount: invoiceAggItems
      .filter((item) => item.status === 'PAST_DUE')
      .reduce((sum, item) => sum + item._count._all, 0),
    paidInvoiceCount: invoiceAggItems
      .filter((item) => item.status === 'PAID')
      .reduce((sum, item) => sum + item._count._all, 0),
    accountsReceivableTotal: invoiceAggItems
      .filter((item) => ['OPEN', 'PAYMENT_PENDING', 'PAST_DUE', 'PARTIALLY_PAID'].includes(item.status))
      .reduce((sum, item) => sum + (item._sum.amountDue ?? 0), 0),
    recognizedRevenueTotal: invoiceAggItems
      .filter((item) => ['PAID', 'PARTIALLY_REFUNDED', 'REFUNDED'].includes(item.status))
      .reduce((sum, item) => sum + (item._sum.totalAmount ?? 0), 0),
  }

  const data = (accounts as any[]).map((account) => {
    const openBalance = (account.invoices as any[])
      .filter((invoice: any) => ['OPEN', 'PAYMENT_PENDING', 'PAST_DUE', 'PARTIALLY_PAID'].includes(invoice.status))
      .reduce((sum: number, invoice: any) => sum + invoice.amountDue, 0)
    const paidBalance = (account.invoices as any[])
      .filter((invoice: any) => ['PAID', 'PARTIALLY_REFUNDED', 'REFUNDED'].includes(invoice.status))
      .reduce((sum: number, invoice: any) => sum + invoice.amountPaid, 0)
    return {
      ...account,
      openBalance,
      paidBalance,
      creditBalance: (account.creditBalances as any[]).reduce((sum: number, item: any) => sum + item.balanceAmount, 0),
    }
  })

  return { data, total, stats }
}

export async function getBillingAccountDetail(companyId: string) {
  await syncLegacySubscriptionInvoices(companyId)
  await ensurePrimaryBillingAccount(companyId)

  const account = await prisma.billingAccount.findFirst({
    where: { companyId, isPrimary: true },
    include: {
      company: {
        include: {
          subscription: true,
          contractSettings: true,
          accountingSettings: true,
        },
      },
      paymentMethods: true,
      creditBalances: true,
      creditLedgerEntries: {
        orderBy: { createdAt: 'desc' },
        take: 50,
      },
      events: {
        orderBy: { createdAt: 'desc' },
        take: 50,
      },
      invoices: {
        orderBy: { createdAt: 'desc' },
        take: 50,
        include: {
          lineItems: { orderBy: { createdAt: 'asc' } },
          paymentIntents: { orderBy: { createdAt: 'desc' } },
          paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
          taxRecords: true,
          creditNotes: { orderBy: { createdAt: 'desc' } },
          refunds: { orderBy: { createdAt: 'desc' } },
        },
      },
    },
  })

  if (!account) throw new NotFoundError('Billing account not found')
  return account
}

export async function updateBillingAccount(
  billingAccountId: string,
  data: {
    legalName?: string
    billingEmail?: string
    billingAddress?: any
    taxId?: string | null
    taxExempt?: boolean
    invoiceTerms?: string
    netTermsDays?: number
  },
  adminId: string,
  ip?: string,
) {
  const before = await prisma.billingAccount.findUniqueOrThrow({ where: { id: billingAccountId } })
  const updated = await prisma.billingAccount.update({
    where: { id: billingAccountId },
    data: {
      ...(data.legalName !== undefined ? { legalName: data.legalName } : {}),
      ...(data.billingEmail !== undefined ? { billingEmail: data.billingEmail } : {}),
      ...(data.billingAddress !== undefined ? { billingAddress: normalizeAddress(data.billingAddress) } : {}),
      ...(data.taxId !== undefined ? { taxId: data.taxId } : {}),
      ...(data.taxExempt !== undefined ? { taxExempt: data.taxExempt } : {}),
      ...(data.invoiceTerms !== undefined ? { invoiceTerms: data.invoiceTerms as any } : {}),
      ...(data.netTermsDays !== undefined ? { netTermsDays: data.netTermsDays } : {}),
    },
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'UPDATE_BILLING_ACCOUNT',
    resource: 'BillingAccount',
    resourceId: billingAccountId,
    companyId: before.companyId,
    before,
    after: updated,
    ipAddress: ip,
  })

  await prisma.billingEvent.create({
    data: {
      billingAccountId,
      companyId: before.companyId,
      eventType: 'billing_account.updated',
      source: 'admin',
      payload: data as any,
      occurredAt: new Date(),
    },
  })

  return updated
}

export async function setDunningPaused(
  billingAccountId: string,
  paused: boolean,
  adminId: string,
  ip?: string,
) {
  const account = await prisma.billingAccount.findUniqueOrThrow({ where: { id: billingAccountId } })
  const updated = await prisma.billingAccount.update({
    where: { id: billingAccountId },
    data: {
      dunningPaused: paused,
      dunningPausedAt: paused ? new Date() : null,
      dunningPausedBy: paused ? adminId : null,
    },
  })

  await createAuditLog({
    adminUserId: adminId,
    action: paused ? 'PAUSE_DUNNING' : 'RESUME_DUNNING',
    resource: 'BillingAccount',
    resourceId: billingAccountId,
    companyId: account.companyId,
    after: { dunningPaused: paused },
    ipAddress: ip,
  })

  await prisma.billingEvent.create({
    data: {
      billingAccountId,
      companyId: account.companyId,
      eventType: paused ? 'dunning.paused' : 'dunning.resumed',
      source: 'admin',
      payload: { pausedBy: adminId },
      occurredAt: new Date(),
    },
  })

  return updated
}

export async function createDraftInvoice(
  billingAccountId: string,
  data: {
    subscriptionId?: string | null
    invoiceType: string
    currency?: string
    dueAt?: string | null
    isSubscriptionBlocking?: boolean
    adminReason?: string | null
    lineItems: Array<{
      type: string
      description: string
      quantity: number
      unitAmount: number
      periodStart?: string | null
      periodEnd?: string | null
    }>
  },
  adminId: string,
  ip?: string,
) {
  if (!data.lineItems.length) throw new ValidationError('Invoice requires at least one line item')

  const invoice = await prisma.$transaction(async (tx: any) => {
    const account = await tx.billingAccount.findUniqueOrThrow({
      where: { id: billingAccountId },
      include: { company: true },
    })

    const created = await tx.billingInvoice.create({
      data: {
        billingAccountId,
        companyId: account.companyId,
        subscriptionId: data.subscriptionId ?? null,
        invoiceType: data.invoiceType as any,
        status: 'DRAFT',
        currency: data.currency ?? account.defaultCurrency,
        dueAt: data.dueAt ? new Date(data.dueAt) : null,
        isSubscriptionBlocking: data.isSubscriptionBlocking ?? isBlockingInvoiceType(data.invoiceType),
        adminReason: data.adminReason ?? null,
        createdByAdminId: adminId,
        billingName: account.legalName,
        billingEmail: account.billingEmail,
        billingAddress: normalizeAddress(account.billingAddress) ?? undefined,
        metadata: {},
        lineItems: {
          create: data.lineItems.map((item) => ({
            type: item.type as any,
            description: item.description,
            quantity: item.quantity,
            unitAmount: item.unitAmount,
            amount: item.quantity * item.unitAmount,
            currency: data.currency ?? account.defaultCurrency,
            periodStart: item.periodStart ? new Date(item.periodStart) : null,
            periodEnd: item.periodEnd ? new Date(item.periodEnd) : null,
          })),
        },
      },
      include: { lineItems: true },
    })

    await createBillingEvent(tx, {
      billingAccountId,
      invoiceId: created.id,
      subscriptionId: created.subscriptionId,
      companyId: account.companyId,
      eventType: 'invoice.created',
      source: 'admin',
      payload: { invoiceType: created.invoiceType, lineItemCount: data.lineItems.length },
    })

    return created
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'CREATE_BILLING_INVOICE',
    resource: 'BillingInvoice',
    resourceId: invoice.id,
    companyId: invoice.companyId,
    after: invoice,
    note: data.adminReason ?? undefined,
    ipAddress: ip,
  })

  return invoice
}

export async function finalizeInvoice(invoiceId: string, adminId: string, ip?: string) {
  const invoice = await prisma.$transaction(async (tx: any) => {
    const current = await tx.billingInvoice.findUnique({
      where: { id: invoiceId },
      include: {
        billingAccount: {
          include: {
            company: { include: { contractSettings: true } },
            creditBalances: true,
          },
        },
        lineItems: true,
      },
    })

    if (!current) throw new NotFoundError('Invoice not found')
    if (current.status !== 'DRAFT') throw new ValidationError('Only draft invoices can be finalized')
    if (!current.lineItems.length) throw new ValidationError('Invoice requires at least one line item')

    const account = current.billingAccount
    const currency = current.currency || account.defaultCurrency
    const existingCreditAmount = Math.abs(
      current.lineItems.filter((item: any) => item.type === 'CREDIT').reduce((sum: number, item: any) => sum + item.amount, 0),
    )
    const { subtotalAmount, discountAmount } = calculateLineAmounts(current.lineItems)
    const availableBalance = account.creditBalances.find((balance: any) => balance.currency === currency)?.balanceAmount ?? 0
    const preTaxBase = Math.max(subtotalAmount - discountAmount - existingCreditAmount, 0)
    const autoCreditToApply = Math.min(availableBalance, preTaxBase)

    if (autoCreditToApply > 0) {
      await tx.billingInvoiceLineItem.create({
        data: {
          invoiceId: current.id,
          type: 'CREDIT',
          description: 'Account credit applied',
          quantity: 1,
          unitAmount: -autoCreditToApply,
          amount: -autoCreditToApply,
          currency,
          metadata: { autoApplied: true },
        },
      })

      await tx.billingCreditBalance.update({
        where: {
          billingAccountId_currency: {
            billingAccountId: account.id,
            currency,
          },
        },
        data: {
          balanceAmount: { decrement: autoCreditToApply },
        },
      })

      await tx.billingCreditLedgerEntry.create({
        data: {
          billingAccountId: account.id,
          invoiceId: current.id,
          type: 'applied',
          amount: -autoCreditToApply,
          currency,
          reason: 'Applied automatically on invoice finalization',
          createdBy: adminId,
        },
      })
    }

    const taxRate = account.taxExempt ? 0 : Number(account.company.contractSettings?.taxRate ?? 0)
    const taxBase = Math.max(preTaxBase - autoCreditToApply, 0)
    const taxAmount = taxRate > 0 ? Math.round(taxBase * (taxRate / 100)) : 0

    if (taxAmount > 0) {
      await tx.billingInvoiceLineItem.create({
        data: {
          invoiceId: current.id,
          type: 'TAX',
          description: `Tax (${taxRate}%)`,
          quantity: 1,
          unitAmount: taxAmount,
          amount: taxAmount,
          currency,
        },
      })

      await tx.billingTaxRecord.create({
        data: {
          invoiceId: current.id,
          taxRate,
          taxAmount,
          taxType: 'VAT',
          taxExempt: false,
          metadata: {},
        },
      })
    } else if (account.taxExempt) {
      await tx.billingTaxRecord.create({
        data: {
          invoiceId: current.id,
          taxRate: 0,
          taxAmount: 0,
          taxType: 'EXEMPT',
          taxExempt: true,
          exemptionReason: 'Billing account marked tax exempt',
          metadata: {},
        },
      })
    }

    const finalLineItems = await tx.billingInvoiceLineItem.findMany({ where: { invoiceId: current.id } })
    const amounts = calculateLineAmounts(finalLineItems)
    const finalizedAt = new Date()
    const sequence = await getNextInvoiceSequence(tx)
    const invoiceNumber = buildSequentialInvoiceNumber(sequence, finalizedAt)
    const dueAt = current.dueAt ?? addDays(finalizedAt, getInvoiceTermsDays(account.invoiceTerms, account.netTermsDays))
    const status = amounts.totalAmount === 0 ? 'PAID' : 'OPEN'

    const updated = await tx.billingInvoice.update({
      where: { id: current.id },
      data: {
        invoiceNumber,
        invoiceSequence: sequence,
        status,
        subtotalAmount: amounts.subtotalAmount,
        discountAmount: amounts.discountAmount,
        creditAmount: amounts.creditAmount,
        taxAmount: amounts.taxAmount,
        totalAmount: amounts.totalAmount,
        amountPaid: 0,
        amountDue: amounts.totalAmount,
        invoiceDate: finalizedAt,
        dueAt,
        finalizedAt,
        paidAt: status === 'PAID' ? finalizedAt : null,
        billingName: account.legalName,
        billingEmail: account.billingEmail,
        billingAddress: normalizeAddress(account.billingAddress) ?? undefined,
      },
      include: {
        lineItems: { orderBy: { createdAt: 'asc' } },
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        taxRecords: true,
        creditNotes: true,
        refunds: true,
      },
    })

    await createBillingEvent(tx, {
      billingAccountId: account.id,
      invoiceId: updated.id,
      subscriptionId: updated.subscriptionId,
      companyId: updated.companyId,
      eventType: 'invoice.finalized',
      source: 'admin',
      payload: { invoiceNumber, totalAmount: updated.totalAmount },
    })

    if (updated.status === 'PAID') {
      await createBillingEvent(tx, {
        billingAccountId: account.id,
        invoiceId: updated.id,
        subscriptionId: updated.subscriptionId,
        companyId: updated.companyId,
        eventType: 'invoice.paid',
        source: 'system',
        payload: { method: 'credit_balance' },
      })
      await maybeRestoreSubscription(tx, updated)
    }

    return updated
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'FINALIZE_BILLING_INVOICE',
    resource: 'BillingInvoice',
    resourceId: invoiceId,
    companyId: invoice.companyId,
    after: { status: invoice.status, invoiceNumber: invoice.invoiceNumber },
    ipAddress: ip,
  })

  return invoice
}

export async function payInvoice(
  invoiceId: string,
  data: {
    amount?: number
    paymentMethodId?: string | null
    providerPaymentId?: string | null
  },
  adminId: string,
  ip?: string,
) {
  const invoice = await prisma.$transaction(async (tx: any) => {
    const current = await tx.billingInvoice.findUniqueOrThrow({
      where: { id: invoiceId },
      include: {
        billingAccount: true,
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
      },
    })

    if (!['OPEN', 'PAYMENT_PENDING', 'PAST_DUE', 'PARTIALLY_PAID'].includes(current.status)) {
      throw new ValidationError('Invoice is not payable in its current state')
    }

    const amount = data.amount ?? current.amountDue
    if (amount <= 0 || amount > current.amountDue) {
      throw new ValidationError('Payment amount must be greater than zero and no more than the invoice balance')
    }

    const intent = await tx.billingPaymentIntent.create({
      data: {
        invoiceId: current.id,
        billingAccountId: current.billingAccountId,
        paymentMethodId: data.paymentMethodId ?? null,
        providerPaymentIntentId: data.providerPaymentId ?? null,
        status: 'SUCCEEDED',
        amount,
        currency: current.currency,
        metadata: { source: 'admin_manual_collection' },
      },
    })

    await tx.billingPaymentAttempt.create({
      data: {
        invoiceId: current.id,
        billingAccountId: current.billingAccountId,
        paymentIntentId: intent.id,
        paymentMethodId: data.paymentMethodId ?? null,
        providerPaymentId: data.providerPaymentId ?? null,
        status: 'SUCCEEDED',
        amount,
        currency: current.currency,
        attemptedAt: new Date(),
        metadata: { source: 'admin_manual_collection' },
      },
    })

    const nextAmountPaid = current.amountPaid + amount
    const nextAmountDue = Math.max(current.amountDue - amount, 0)
    const nextStatus = nextAmountDue === 0 ? 'PAID' : 'PARTIALLY_PAID'

    const updated = await tx.billingInvoice.update({
      where: { id: current.id },
      data: {
        amountPaid: nextAmountPaid,
        amountDue: nextAmountDue,
        status: nextStatus,
        paidAt: nextAmountDue === 0 ? new Date() : current.paidAt,
      },
      include: {
        lineItems: { orderBy: { createdAt: 'asc' } },
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        taxRecords: true,
        creditNotes: true,
        refunds: true,
      },
    })

    await createBillingEvent(tx, {
      billingAccountId: updated.billingAccountId,
      invoiceId: updated.id,
      subscriptionId: updated.subscriptionId,
      companyId: updated.companyId,
      eventType: nextAmountDue === 0 ? 'invoice.paid' : 'invoice.partially_paid',
      source: 'admin',
      payload: { amount, amountPaid: nextAmountPaid, amountDue: nextAmountDue },
    })

    if (nextAmountDue === 0) {
      await maybeRestoreSubscription(tx, updated)
    }

    return updated
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'PAY_BILLING_INVOICE',
    resource: 'BillingInvoice',
    resourceId: invoiceId,
    companyId: invoice.companyId,
    after: { amountPaid: invoice.amountPaid, amountDue: invoice.amountDue, status: invoice.status },
    ipAddress: ip,
  })

  return invoice
}

export async function retryInvoicePayment(
  invoiceId: string,
  data: { paymentMethodId?: string | null },
  adminId: string,
  ip?: string,
) {
  const invoice = await prisma.$transaction(async (tx: any) => {
    const current = await tx.billingInvoice.findUniqueOrThrow({
      where: { id: invoiceId },
      include: { billingAccount: true },
    })

    if (!['OPEN', 'PAST_DUE', 'PARTIALLY_PAID'].includes(current.status)) {
      throw new ValidationError('Only open or overdue invoices can be retried')
    }

    const intent = await tx.billingPaymentIntent.create({
      data: {
        invoiceId: current.id,
        billingAccountId: current.billingAccountId,
        paymentMethodId: data.paymentMethodId ?? current.billingAccount.defaultPaymentMethodId ?? null,
        status: 'PROCESSING',
        amount: current.amountDue,
        currency: current.currency,
        metadata: { source: 'admin_retry' },
      },
    })

    await tx.billingPaymentAttempt.create({
      data: {
        invoiceId: current.id,
        billingAccountId: current.billingAccountId,
        paymentIntentId: intent.id,
        paymentMethodId: data.paymentMethodId ?? current.billingAccount.defaultPaymentMethodId ?? null,
        status: 'PENDING',
        amount: current.amountDue,
        currency: current.currency,
        attemptedAt: new Date(),
        metadata: { source: 'admin_retry' },
      },
    })

    const updated = await tx.billingInvoice.update({
      where: { id: current.id },
      data: { status: 'PAYMENT_PENDING' },
      include: {
        lineItems: { orderBy: { createdAt: 'asc' } },
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        taxRecords: true,
        creditNotes: true,
        refunds: true,
      },
    })

    await createBillingEvent(tx, {
      billingAccountId: updated.billingAccountId,
      invoiceId: updated.id,
      subscriptionId: updated.subscriptionId,
      companyId: updated.companyId,
      eventType: 'invoice.payment_started',
      source: 'admin',
      payload: { amountDue: updated.amountDue },
    })

    return updated
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'RETRY_BILLING_INVOICE_PAYMENT',
    resource: 'BillingInvoice',
    resourceId: invoiceId,
    companyId: invoice.companyId,
    after: { status: invoice.status },
    ipAddress: ip,
  })

  return invoice
}

export async function voidInvoice(invoiceId: string, reason: string, adminId: string, ip?: string) {
  const invoice = await prisma.$transaction(async (tx: any) => {
    const current = await tx.billingInvoice.findUniqueOrThrow({ where: { id: invoiceId } })
    if (!['DRAFT', 'OPEN', 'PAYMENT_PENDING', 'PAST_DUE', 'PARTIALLY_PAID'].includes(current.status)) {
      throw new ValidationError('Only unpaid invoices can be voided')
    }
    if (current.amountPaid > 0) {
      throw new ValidationError('Paid invoices cannot be voided through this workflow')
    }

    const updated = await tx.billingInvoice.update({
      where: { id: invoiceId },
      data: {
        status: 'VOID',
        amountDue: 0,
        voidedAt: new Date(),
      },
      include: {
        lineItems: { orderBy: { createdAt: 'asc' } },
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        taxRecords: true,
        creditNotes: true,
        refunds: true,
      },
    })

    await createBillingEvent(tx, {
      billingAccountId: updated.billingAccountId,
      invoiceId: updated.id,
      subscriptionId: updated.subscriptionId,
      companyId: updated.companyId,
      eventType: 'invoice.voided',
      source: 'admin',
      payload: { reason },
    })

    return updated
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'VOID_BILLING_INVOICE',
    resource: 'BillingInvoice',
    resourceId: invoiceId,
    companyId: invoice.companyId,
    note: reason,
    after: { status: invoice.status },
    ipAddress: ip,
  })

  return invoice
}

export async function markInvoiceUncollectible(invoiceId: string, reason: string, adminId: string, ip?: string) {
  const invoice = await prisma.$transaction(async (tx: any) => {
    const current = await tx.billingInvoice.findUniqueOrThrow({ where: { id: invoiceId } })
    if (!['OPEN', 'PAST_DUE', 'PAYMENT_PENDING', 'PARTIALLY_PAID'].includes(current.status)) {
      throw new ValidationError('Only open invoices can be marked uncollectible')
    }

    const updated = await tx.billingInvoice.update({
      where: { id: invoiceId },
      data: {
        status: 'UNCOLLECTIBLE',
        markedUncollectibleAt: new Date(),
      },
      include: {
        lineItems: { orderBy: { createdAt: 'asc' } },
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        taxRecords: true,
        creditNotes: true,
        refunds: true,
      },
    })

    await createBillingEvent(tx, {
      billingAccountId: updated.billingAccountId,
      invoiceId: updated.id,
      subscriptionId: updated.subscriptionId,
      companyId: updated.companyId,
      eventType: 'invoice.marked_uncollectible',
      source: 'admin',
      payload: { reason },
    })

    await maybeCancelSubscriptionForWriteoff(tx, updated)
    return updated
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'MARK_BILLING_INVOICE_UNCOLLECTIBLE',
    resource: 'BillingInvoice',
    resourceId: invoiceId,
    companyId: invoice.companyId,
    note: reason,
    after: { status: invoice.status },
    ipAddress: ip,
  })

  return invoice
}

export async function issueCreditNote(
  invoiceId: string,
  data: { amount: number; reason: string },
  adminId: string,
  ip?: string,
) {
  const invoice = await prisma.$transaction(async (tx: any) => {
    const current = await tx.billingInvoice.findUniqueOrThrow({
      where: { id: invoiceId },
      include: { creditNotes: true },
    })
    if (!EDITABLE_BILLING_STATUSES.has(current.status)) {
      throw new ValidationError('Credit notes can only be issued against active receivable invoices')
    }
    if (data.amount <= 0 || data.amount > current.amountDue) {
      throw new ValidationError('Credit note amount must be positive and no more than the outstanding balance')
    }

    await tx.billingCreditNote.create({
      data: {
        invoiceId,
        billingAccountId: current.billingAccountId,
        amount: data.amount,
        currency: current.currency,
        reason: data.reason,
        status: 'APPLIED',
        createdBy: adminId,
      },
    })

    const nextTotalAmount = Math.max(current.totalAmount - data.amount, 0)
    const nextAmountDue = Math.max(current.amountDue - data.amount, 0)

    const updated = await tx.billingInvoice.update({
      where: { id: invoiceId },
      data: {
        creditAmount: current.creditAmount + data.amount,
        totalAmount: nextTotalAmount,
        amountDue: nextAmountDue,
        status: nextAmountDue === 0 ? 'PAID' : current.status,
        paidAt: nextAmountDue === 0 ? new Date() : current.paidAt,
      },
      include: {
        lineItems: { orderBy: { createdAt: 'asc' } },
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        taxRecords: true,
        creditNotes: { orderBy: { createdAt: 'desc' } },
        refunds: true,
      },
    })

    await createBillingEvent(tx, {
      billingAccountId: updated.billingAccountId,
      invoiceId: updated.id,
      subscriptionId: updated.subscriptionId,
      companyId: updated.companyId,
      eventType: 'credit.applied',
      source: 'admin',
      payload: { amount: data.amount, reason: data.reason },
    })

    if (nextAmountDue === 0) {
      await maybeRestoreSubscription(tx, updated)
    }

    return updated
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'ISSUE_BILLING_CREDIT_NOTE',
    resource: 'BillingInvoice',
    resourceId: invoiceId,
    companyId: invoice.companyId,
    note: data.reason,
    after: { creditAmount: invoice.creditAmount, amountDue: invoice.amountDue, status: invoice.status },
    ipAddress: ip,
  })

  return invoice
}

export async function issueRefund(
  invoiceId: string,
  data: { amount: number; reason: string },
  adminId: string,
  ip?: string,
) {
  const invoice = await prisma.$transaction(async (tx: any) => {
    const current = await tx.billingInvoice.findUniqueOrThrow({
      where: { id: invoiceId },
      include: {
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        refunds: true,
      },
    })
    if (!['PAID', 'PARTIALLY_REFUNDED', 'REFUNDED'].includes(current.status)) {
      throw new ValidationError('Only paid invoices can be refunded')
    }

    const refundedSoFar = current.refunds
      .filter((refund: any) => refund.status === 'SUCCEEDED')
      .reduce((sum: number, refund: any) => sum + refund.amount, 0)
    const refundableAmount = Math.max(current.totalAmount - refundedSoFar, 0)
    if (data.amount <= 0 || data.amount > refundableAmount) {
      throw new ValidationError('Refund amount must be positive and within the refundable amount')
    }

    const latestSuccessfulAttempt = current.paymentAttempts.find((attempt: any) => attempt.status === 'SUCCEEDED') ?? null

    await tx.billingRefund.create({
      data: {
        invoiceId,
        paymentAttemptId: latestSuccessfulAttempt?.id ?? null,
        billingAccountId: current.billingAccountId,
        amount: data.amount,
        currency: current.currency,
        reason: data.reason,
        status: 'SUCCEEDED',
        createdBy: adminId,
      },
    })

    const totalRefunded = refundedSoFar + data.amount
    const nextStatus = totalRefunded >= current.totalAmount ? 'REFUNDED' : 'PARTIALLY_REFUNDED'

    const updated = await tx.billingInvoice.update({
      where: { id: invoiceId },
      data: { status: nextStatus },
      include: {
        lineItems: { orderBy: { createdAt: 'asc' } },
        paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
        taxRecords: true,
        creditNotes: true,
        refunds: { orderBy: { createdAt: 'desc' } },
      },
    })

    await createBillingEvent(tx, {
      billingAccountId: updated.billingAccountId,
      invoiceId: updated.id,
      subscriptionId: updated.subscriptionId,
      companyId: updated.companyId,
      eventType: 'refund.succeeded',
      source: 'admin',
      payload: { amount: data.amount, reason: data.reason },
    })

    return updated
  })

  await createAuditLog({
    adminUserId: adminId,
    action: 'ISSUE_BILLING_REFUND',
    resource: 'BillingInvoice',
    resourceId: invoiceId,
    companyId: invoice.companyId,
    note: data.reason,
    after: { status: invoice.status },
    ipAddress: ip,
  })

  return invoice
}

export async function getInvoicePdf(invoiceId: string) {
  await syncLegacySubscriptionInvoices()

  const invoice = await prisma.billingInvoice.findUnique({
    where: { id: invoiceId },
    include: {
      company: true,
      subscription: true,
      lineItems: { orderBy: { createdAt: 'asc' } },
      paymentAttempts: { orderBy: { attemptedAt: 'desc' } },
      taxRecords: true,
      creditNotes: { orderBy: { createdAt: 'desc' } },
      refunds: { orderBy: { createdAt: 'desc' } },
    },
  })
  if (!invoice) throw new NotFoundError('Invoice not found')
  if (!invoice.invoiceNumber) throw new ValidationError('Invoice must be finalized before a PDF can be generated')

  const latestPaymentAttempt = invoice.paymentAttempts.find((attempt: any) => attempt.status === 'SUCCEEDED') ?? invoice.paymentAttempts[0] ?? null
  const pdfBuffer = await generateInvoicePdf({
    invoiceNumber: invoice.invoiceNumber,
    issueDate: invoice.invoiceDate?.toISOString() ?? invoice.createdAt.toISOString(),
    dueDate: invoice.dueAt?.toISOString() ?? null,
    company: {
      name: invoice.billingName ?? invoice.company.name,
      email: invoice.billingEmail ?? invoice.company.email,
      phone: invoice.company.phone,
      address: invoice.billingAddress ?? invoice.company.address,
    },
    subscription: invoice.subscription
      ? {
          plan: invoice.subscription.plan,
          billingPeriod: invoice.subscription.billingPeriod,
          currentPeriodStart: invoice.subscription.currentPeriodStart?.toISOString(),
          currentPeriodEnd: invoice.subscription.currentPeriodEnd?.toISOString(),
          currency: invoice.subscription.currency,
        }
      : undefined,
    amount: invoice.totalAmount,
    currency: invoice.currency,
    status: invoice.status,
    paymentProvider: invoice.paymentProvider ?? 'MANUAL',
    transactionId: latestPaymentAttempt?.providerPaymentId ?? null,
    paidAt: invoice.paidAt?.toISOString(),
    lineItems: invoice.lineItems.map((item: any) => ({
      description: item.description,
      amount: item.amount,
      currency: item.currency,
      quantity: item.quantity,
      unitAmount: item.unitAmount,
      periodStart: item.periodStart?.toISOString() ?? null,
      periodEnd: item.periodEnd?.toISOString() ?? null,
    })),
    totals: {
      subtotalAmount: invoice.subtotalAmount,
      discountAmount: invoice.discountAmount,
      creditAmount: invoice.creditAmount,
      taxAmount: invoice.taxAmount,
      totalAmount: invoice.totalAmount,
      amountPaid: invoice.amountPaid,
      amountDue: invoice.amountDue,
    },
  })

  return { pdfBuffer, invoiceNumber: invoice.invoiceNumber }
}
