/**
 * Portal invoices routes — tenant-portals (wave 9d, Task 7).
 *
 * GET  /api/portal/invoices
 * GET  /api/portal/invoices/:id/html
 * POST /api/portal/invoices/:id/pay
 */
import { Hono } from 'hono'
import { serializeInvoice } from '@zync/public-api'
import type { Env } from '@zync/types'
import {
  getPortalInvoices,
  getInvoiceWithLines,
  renderInvoiceHtml,
  getPaymentGatewayConfig,
  PORTAL_INVOICE_STATUSES,
  type PortalInvoiceStatus,
} from '@zync/db/queries'
import {
  fetchInvoiceHtmlSnapshot,
  generateAndStoreInvoiceSnapshot,
  loadInvoiceRenderIdentity,
} from '../../lib/invoice-snapshot'
import { requirePortalVisibility } from './visibility'
import type { AppEnv } from '../../types'
import {
  portalAuthMiddleware,
  requirePortalScope,
  type PortalAuthVariables,
} from '../../middleware/portalAuth'
import { generatePaymentLink as cardcomLink } from '../../integrations/payment-gateways/cardcom'
import { generatePaymentLink as payplusLink } from '../../integrations/payment-gateways/payplus'
import { generatePaymentLink as stripeLink } from '../../integrations/payment-gateways/stripe'

type PortalDataEnv = {
  Bindings: AppEnv['Bindings']
  Variables: PortalAuthVariables
}

export const portalInvoicesRoutes = new Hono<PortalDataEnv>()

portalInvoicesRoutes.use('*', portalAuthMiddleware)

portalInvoicesRoutes.get('/', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_invoices')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const statusParam = c.req.query('status')

  if (statusParam && !PORTAL_INVOICE_STATUSES.includes(statusParam as PortalInvoiceStatus)) {
    return c.json({ error: 'Invalid status filter' }, 400)
  }

  const rows = await getPortalInvoices(
    db,
    portal.tenantId,
    portal.customerId,
    statusParam as PortalInvoiceStatus | undefined,
  )

  const items = rows.map((row) =>
    serializeInvoice(
      {
        id: row.id,
        customerId: portal.customerId,
        projectId: null,
        invoiceNumber: row.invoiceNumber,
        proformaNumber: row.proformaNumber,
        status: row.status,
        currency: row.currency,
        issueDate: row.issueDate,
        taxIssueDate: null,
        dueDate: row.dueDate,
        vatRate: null,
        subtotal: row.subtotal,
        vatAmount: row.vatAmount,
        total: row.total,
        notes: null,
        source: 'portal',
        paidAt: null,
        createdAt: row.createdAt,
        updatedAt: row.createdAt,
      },
      [],
    ),
  )

  return c.json({ items }, 200)
})

portalInvoicesRoutes.get('/:id', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_invoices')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const invoiceId = c.req.param('id')

  const invoice = await getInvoiceWithLines(db, portal.tenantId, invoiceId)
  if (!invoice) return c.json({ error: 'Not found' }, 404)

  const scopeErr = requirePortalScope(invoice.customerId ?? '', portal)
  if (scopeErr) return c.json({ error: 'Not found' }, 404)

  if (invoice.status === 'DRAFT' || invoice.status === 'VOID') {
    return c.json({ error: 'Not found' }, 404)
  }

  const lines = invoice.lines.map((line) => ({
    id: line.id,
    description: line.description,
    quantity: line.quantity,
    unit_price: line.unitPrice,
    discount_pct: line.discountPct,
    line_total: line.lineTotal,
    taxable: line.taxable,
    position: line.position,
  }))

  return c.json({
    invoice: serializeInvoice(
      {
        id: invoice.id,
        customerId: invoice.customerId,
        projectId: invoice.projectId,
        invoiceNumber: invoice.invoiceNumber,
        proformaNumber: invoice.proformaNumber,
        status: invoice.status,
        currency: invoice.currency,
        issueDate: invoice.issueDate,
        taxIssueDate: invoice.taxIssueDate,
        dueDate: invoice.dueDate,
        vatRate: invoice.vatRate,
        subtotal: invoice.subtotal,
        vatAmount: invoice.vatAmount,
        total: invoice.total,
        notes: invoice.notes,
        source: invoice.source,
        paidAt: invoice.paidAt,
        createdAt: invoice.createdAt,
        updatedAt: invoice.updatedAt,
      },
      [],
    ),
    lines,
  }, 200)
})

portalInvoicesRoutes.get('/:id/html', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_invoices')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const invoiceId = c.req.param('id')

  const invoice = await getInvoiceWithLines(db, portal.tenantId, invoiceId)
  if (!invoice) return c.json({ error: 'Not found' }, 404)

  const scopeErr = requirePortalScope(invoice.customerId ?? '', portal)
  if (scopeErr) return c.json({ error: 'Not found' }, 404)

  if (invoice.status === 'DRAFT' || invoice.status === 'VOID') {
    return c.json({ error: 'Not found' }, 404)
  }

  const htmlResponseHeaders = {
    'Content-Type': 'text/html; charset=utf-8',
    'Content-Security-Policy':
      "default-src 'none'; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data:",
    'X-Content-Type-Options': 'nosniff',
  } as const

  // TAX_ISSUED: serve immutable R2 snapshot when available (key stored in htmlSnapshotUrl)
  if (invoice.status === 'TAX_ISSUED') {
    const r2 = c.env.INVOICE_SNAPSHOTS_BUCKET
    let snapshotKey = invoice.htmlSnapshotUrl

    if (snapshotKey) {
      const snapshotHtml = await fetchInvoiceHtmlSnapshot(r2, snapshotKey)
      if (snapshotHtml) {
        return new Response(snapshotHtml, { status: 200, headers: htmlResponseHeaders })
      }
    }

    if (r2) {
      try {
        const snapshotted = await generateAndStoreInvoiceSnapshot(
          { db, env: c.env },
          portal.tenantId,
          invoiceId,
        )
        snapshotKey = snapshotted?.htmlSnapshotUrl ?? snapshotKey
        if (snapshotKey) {
          const snapshotHtml = await fetchInvoiceHtmlSnapshot(r2, snapshotKey)
          if (snapshotHtml) {
            return new Response(snapshotHtml, { status: 200, headers: htmlResponseHeaders })
          }
        }
      } catch (backfillErr) {
        console.error('[portal/invoices/html] snapshot backfill failed:', backfillErr)
      }
    }
  }

  const identity = await loadInvoiceRenderIdentity(db, portal.tenantId, invoice.customerId)
  const r2PublicUrl =
    (c.env as Env & { INVOICE_SNAPSHOTS_PUBLIC_URL?: string }).INVOICE_SNAPSHOTS_PUBLIC_URL?.trim() ||
    undefined
  const html = renderInvoiceHtml(invoice, {
    ...identity,
    r2PublicUrl,
  })

  return new Response(html, {
    status: 200,
    headers: htmlResponseHeaders,
  })
})

portalInvoicesRoutes.get('/:id/pdf', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_invoices')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const invoiceId = c.req.param('id')
  const invoice = await getInvoiceWithLines(db, portal.tenantId, invoiceId)
  if (!invoice) return c.json({ error: 'Not found' }, 404)

  const scopeErr = requirePortalScope(invoice.customerId ?? '', portal)
  if (scopeErr) return c.json({ error: 'Not found' }, 404)
  if (invoice.status === 'DRAFT' || invoice.status === 'VOID') {
    return c.json({ error: 'Not found' }, 404)
  }

  const bucket = c.env.INVOICE_SNAPSHOTS_BUCKET as R2Bucket & {
    createPresignedUrl?: (
      method: string,
      key: string,
      options: { expiresIn: number },
    ) => Promise<string>
  }
  let snapshotKey = invoice.htmlSnapshotUrl

  if (!snapshotKey && c.env.INVOICE_SNAPSHOTS_BUCKET) {
    try {
      const snapshotted = await generateAndStoreInvoiceSnapshot(
        { db, env: c.env },
        portal.tenantId,
        invoiceId,
      )
      snapshotKey = snapshotted?.htmlSnapshotUrl ?? null
    } catch (err) {
      console.error('[portal/invoices/pdf] snapshot backfill failed:', err)
    }
  }

  if (snapshotKey && typeof bucket?.createPresignedUrl === 'function') {
    const url = await bucket.createPresignedUrl('GET', snapshotKey, { expiresIn: 900 })
    return c.redirect(url, 302)
  }

  return c.redirect(`/api/portal/invoices/${encodeURIComponent(invoiceId)}/html`, 302)
})

portalInvoicesRoutes.post('/:id/pay', async (c) => {
  const portal = c.get('portal')
  const db = c.get('db')
  const invoiceId = c.req.param('id')

  const invoice = await getInvoiceWithLines(db, portal.tenantId, invoiceId)
  if (!invoice) return c.json({ error: 'Not found' }, 404)

  const scopeErr = requirePortalScope(invoice.customerId ?? '', portal)
  if (scopeErr) return c.json({ error: 'Not found' }, 404)

  if (invoice.status !== 'TAX_ISSUED') {
    return c.json({ error: 'Invoice is not payable' }, 409)
  }

  const outstanding = parseFloat(invoice.total) - parseFloat(invoice.amountPaid)
  if (outstanding <= 0) {
    return c.json({ error: 'Invoice is already paid' }, 409)
  }

  const gatewayConfig = await getPaymentGatewayConfig(db, portal.tenantId)
  if (!gatewayConfig || !gatewayConfig.isActive) {
    return c.json({ error: 'Payment gateway not configured' }, 409)
  }

  const invoicePayload = {
    id: invoice.id,
    invoiceNumber: invoice.invoiceNumber,
    amount: invoice.total,
    currency: invoice.currency,
    customerName: portal.customerId,
    description: `Invoice ${invoice.invoiceNumber ?? invoice.id}`,
  }

  let redirectUrl: string
  try {
    switch (gatewayConfig.gateway) {
      case 'cardcom': {
        const cfg = gatewayConfig.config as {
          terminalNumber: string
          apiName: string
          apiPassword: string
        }
        const result = await cardcomLink(
          {
            terminalNumber: cfg.terminalNumber,
            apiName: cfg.apiName,
            apiPassword: cfg.apiPassword,
          },
          invoicePayload,
        )
        redirectUrl = result.url
        break
      }
      case 'payplus': {
        const cfg = gatewayConfig.config as { apiKey: string; secretKey: string }
        const result = await payplusLink(
          { apiKey: cfg.apiKey, secretKey: cfg.secretKey },
          invoicePayload,
        )
        redirectUrl = result.url
        break
      }
      case 'stripe': {
        const cfg = gatewayConfig.config as { secretKey: string }
        const result = await stripeLink({ secretKey: cfg.secretKey }, invoicePayload)
        redirectUrl = result.url
        break
      }
      default:
        return c.json({ error: 'Payment gateway not configured' }, 409)
    }
  } catch (err) {
    return c.json({ error: err instanceof Error ? err.message : 'Gateway error' }, 502)
  }

  return c.json({ redirectUrl }, 200)
})
