/**
 * Immutable tax-invoice HTML snapshot — render + R2 put + persist R2 object key.
 * Shared by manual issue-tax, resumable backfill, and auto-issue.
 * htmlSnapshotUrl stores the R2 key (not a public URL); served via GET /api/invoices/:id/html.
 */
import type { R2Bucket } from '@cloudflare/workers-types'
import type { Db, Invoice } from '@zync/db/queries'
import {
  getCustomerWithStats,
  getInvoiceWithLines,
  getTenantById,
  renderInvoiceHtml,
  setInvoiceHtmlSnapshotUrl,
  type Address,
} from '@zync/db/queries'
import type { TenantId } from '@zync/types'
import type { Env } from '@zync/types'

export type InvoiceSnapshotDeps = {
  db: Db
  env: Env
  executionCtx?: { waitUntil(promise: Promise<unknown>): void }
}

export type InvoiceRenderIdentity = {
  tenantName: string
  tenantTaxId?: string
  tenantAddress?: string
  customerName: string
  customerTaxId?: string
  customerAddress?: string
  locale: 'he-IL' | 'en-US'
}

/** Map tenants.settings.locale ('he' | 'en') to BCP-47 tags for invoice HTML. */
export function tenantLocaleToBcp47(stored: string | null | undefined): 'he-IL' | 'en-US' {
  if (stored === 'en' || stored === 'en-US') return 'en-US'
  return 'he-IL'
}

/** Language subtag from a BCP-47 locale (e.g. 'he' from 'he-IL'). */
export function localeLangSubtag(locale: string): string {
  return locale.split('-')[0]?.toLowerCase() || 'he'
}

function formatAddress(addr: Address | string | null | undefined): string | undefined {
  if (!addr) return undefined
  if (typeof addr === 'string') {
    const trimmed = addr.trim()
    return trimmed || undefined
  }
  const parts = [addr.street, addr.city, addr.state, addr.zip, addr.country].filter(Boolean)
  return parts.length > 0 ? parts.join(', ') : undefined
}

function settingsString(settings: Record<string, unknown>, key: string): string | undefined {
  const value = settings[key]
  if (typeof value !== 'string') return undefined
  const trimmed = value.trim()
  return trimmed || undefined
}

/** Load statutory tenant business + customer identity for invoice HTML rendering. */
export async function loadInvoiceRenderIdentity(
  db: Db,
  tenantId: string,
  customerId: string | null,
): Promise<InvoiceRenderIdentity> {
  const [tenant, customerResult] = await Promise.all([
    getTenantById(db, tenantId as TenantId),
    customerId ? getCustomerWithStats(db, tenantId, customerId) : Promise.resolve(null),
  ])

  const settings = (tenant?.settings as Record<string, unknown>) ?? {}
  const locale = tenantLocaleToBcp47(settingsString(settings, 'locale'))
  const tenantName =
    settingsString(settings, 'business_name') ?? tenant?.name?.trim() ?? '—'
  const tenantTaxId =
    settingsString(settings, 'tax_id') ??
    settingsString(settings, 'vat_number') ??
    settingsString(settings, 'business_primary_id')
  const tenantAddress = settingsString(settings, 'address')

  const customer = customerResult?.customer
  const customerName = customer?.name?.trim() ?? '—'
  const customerTaxId = customer?.taxId?.trim() || undefined
  const customerAddress = formatAddress(customer?.address)

  return {
    tenantName,
    tenantTaxId,
    tenantAddress,
    customerName,
    customerTaxId,
    customerAddress,
    locale,
  }
}

function invoiceSnapshotR2Key(
  tenantId: string,
  invoiceId: string,
  invoiceNumber: string,
  lang: string,
): string {
  return `${tenantId}/invoices/${invoiceId}/tax-invoice-${invoiceNumber}-${lang}.html`
}

/** Fetch immutable snapshot HTML from R2 by stored object key. */
export async function fetchInvoiceHtmlSnapshot(
  r2: R2Bucket | undefined,
  key: string,
): Promise<string | null> {
  if (!r2) return null
  const obj = await r2.get(key)
  if (!obj) return null
  return obj.text()
}

/**
 * Render a TAX_ISSUED invoice and store its HTML in R2 (idempotent — safe to retry).
 * Returns the updated invoice when the snapshot key is persisted, null when R2 is not
 * configured (logged), or the loaded invoice when already snapshotted.
 */
export async function generateAndStoreInvoiceSnapshot(
  deps: InvoiceSnapshotDeps,
  tenantId: string,
  invoiceId: string,
  opts?: { locale?: string },
): Promise<Invoice | null> {
  const run = async (): Promise<Invoice | null> => {
    const issued = await getInvoiceWithLines(deps.db, tenantId, invoiceId)
    if (!issued) {
      throw new Error('Invoice not found')
    }
    if (issued.status !== 'TAX_ISSUED') {
      throw new Error(`Cannot snapshot invoice in status ${issued.status}`)
    }
    if (!issued.invoiceNumber) {
      throw new Error('Cannot snapshot tax invoice without invoice number')
    }
    if (issued.htmlSnapshotUrl) {
      return issued
    }

    const r2 = deps.env.INVOICE_SNAPSHOTS_BUCKET
    if (!r2) {
      console.warn('[invoice-snapshot] INVOICE_SNAPSHOTS_BUCKET not bound — skipping HTML snapshot')
      return null
    }

    const r2PublicUrl =
      (deps.env as Env & { INVOICE_SNAPSHOTS_PUBLIC_URL?: string }).INVOICE_SNAPSHOTS_PUBLIC_URL?.trim() ||
      undefined

    const identity = await loadInvoiceRenderIdentity(deps.db, tenantId, issued.customerId)
    const locale = opts?.locale ?? identity.locale
    const html = renderInvoiceHtml(issued, {
      ...identity,
      locale,
      r2PublicUrl,
    })

    const lang = localeLangSubtag(locale)
    const key = invoiceSnapshotR2Key(tenantId, invoiceId, issued.invoiceNumber, lang)
    await r2.put(key, html, { httpMetadata: { contentType: 'text/html; charset=utf-8' } })
    return setInvoiceHtmlSnapshotUrl(deps.db, tenantId, invoiceId, key)
  }

  const work = run()
  if (deps.executionCtx) {
    deps.executionCtx.waitUntil(
      work.catch((err) => {
        console.error('[invoice-snapshot] background snapshot failed:', err)
      }),
    )
  }
  return work
}
