/**
 * Proposal → Invoice query helpers — proposal-to-invoice-direct (wave-11).
 *
 * Maps a proposal's line items to invoice lines and creates a DRAFT invoice.
 * Tenant-filtered; no raw Drizzle from routes.
 */
import { and, eq } from 'drizzle-orm'
import { z } from 'zod'
import type { Db } from '../client'
import { proposals } from '../schema/proposals'
import { invoices, invoiceLines } from '../schema/invoices'
import { tenantSettings } from '../schema/tenants'
import { auditLog } from './_audit-forward'
import { INVOICE_SETTINGS_DEFAULTS } from '@zync/types'
import type { ProposalContent } from '@zync/types'

// ── Zod schemas ───────────────────────────────────────────────────────────────

export const createInvoiceFromProposalSchema = z.object({
  issue_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  due_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  note: z.string().max(5000).optional(),
  lines: z.array(z.object({
    description: z.string().min(1).max(2000),
    quantity: z.number().positive(),
    unit_price: z.number().nonnegative(),
    discount_pct: z.number().min(0).max(100).optional().default(0),
  })).min(1),
})

export type CreateInvoiceFromProposalInput = z.infer<typeof createInvoiceFromProposalSchema>

// ── Types ─────────────────────────────────────────────────────────────────────

export interface ProposalInvoiceLinkResult { invoiceId: string }

export interface CreateInvoiceFromProposalResult {
  invoiceId: string
  invoiceNumber: string | null
}

// ── Helpers ───────────────────────────────────────────────────────────────────

function formatMoney(value: number): string {
  return value.toFixed(2)
}

function mapLineItems(
  lines: CreateInvoiceFromProposalInput['lines'],
  invoiceId: string,
  tenantId: string,
): Array<typeof invoiceLines.$inferInsert> {
  return lines.map((line, idx) => {
    const discountPct = line.discount_pct ?? 0
    const lineTotal = line.quantity * line.unit_price * (1 - discountPct / 100)
    return {
    invoiceId,
    tenantId,
    description: line.description,
    quantity: String(line.quantity),
    unitPrice: String(line.unit_price),
    discountPct: String(discountPct),
    lineTotal: formatMoney(lineTotal),
    taxable: true,
    position: idx,
    }
  })
}

function computeSubtotal(lines: CreateInvoiceFromProposalInput['lines']): number {
  return lines.reduce((sum, line) => {
    const discountPct = line.discount_pct ?? 0
    return sum + line.quantity * line.unit_price * (1 - discountPct / 100)
  }, 0)
}

// ── Query helpers ─────────────────────────────────────────────────────────────

/**
 * Check if an invoice already exists for a proposal.
 * Returns the existing invoice id and status if found.
 */
export async function getProposalInvoiceLink(
  db: Db,
  tenantId: string,
  proposalId: string,
): Promise<ProposalInvoiceLinkResult | null> {
  const [existing] = await db
    .select({ id: invoices.id })
    .from(invoices)
    .where(
      and(
        eq(invoices.tenantId, tenantId),
        eq(invoices.proposalId, proposalId),
      ),
    )
    .limit(1)

  if (!existing) {
    return null
  }
  return { invoiceId: existing.id }
}

/**
 * Create a DRAFT invoice from a proposal.
 * Maps proposal line items to invoice lines.
 * Links invoices.proposal_id = proposalId.
 * Does NOT transition the proposal status.
 */
export async function createInvoiceFromProposal(
  db: Db,
  tenantId: string,
  proposalId: string,
  actorId: string,
  input: CreateInvoiceFromProposalInput,
): Promise<CreateInvoiceFromProposalResult> {
  return db.transaction(async (tx) => {
    const [proposal] = await tx
      .select()
      .from(proposals)
      .where(eq(proposals.id, proposalId))
      .limit(1)

    if (!proposal) throw new Error('Proposal not found')
    if (proposal.tenantId !== tenantId) throw new Error('Forbidden')
    if (proposal.status !== 'accepted') throw new Error('Proposal must be ACCEPTED before creating an invoice')

    // Guard: don't allow duplicate invoice for same proposal
    const [existing] = await tx
      .select({ id: invoices.id })
      .from(invoices)
      .where(
        and(
          eq(invoices.tenantId, tenantId),
          eq(invoices.proposalId, proposalId),
        ),
      )
      .limit(1)

    if (existing) {
      throw new Error(`Invoice already exists for this proposal: ${existing.id}`)
    }

    const [settings] = await tx
      .select({ defaultTaxRate: tenantSettings.defaultTaxRate })
      .from(tenantSettings)
      .where(eq(tenantSettings.tenantId, tenantId))
      .limit(1)

    const proposalContent = proposal.content as ProposalContent | null
    const proposalCurrency = proposalContent?.settings?.currency

    const vatRate = Number(settings?.defaultTaxRate ?? INVOICE_SETTINGS_DEFAULTS.default_tax_rate)
    const subtotalValue = computeSubtotal(input.lines)
    const vatAmountValue = subtotalValue * vatRate
    const totalValue = subtotalValue + vatAmountValue

    const [invoice] = await tx
      .insert(invoices)
      .values({
        tenantId,
        customerId: proposal.customerId ?? undefined,
        status: 'DRAFT',
        currency: proposalCurrency ?? INVOICE_SETTINGS_DEFAULTS.default_currency,
        issueDate: input.issue_date,
        dueDate: input.due_date,
        notes: input.note ?? null,
        subtotal: formatMoney(subtotalValue),
        vatRate: String(vatRate),
        vatAmount: formatMoney(vatAmountValue),
        total: formatMoney(totalValue),
        amountPaid: '0',
        overpaymentAmount: '0',
        source: 'manual',
        proposalId,
        createdBy: actorId,
      })
      .returning({ id: invoices.id })

    const invoiceId = invoice!.id

    // Insert mapped line items
    if (input.lines.length > 0) {
      const mapped = mapLineItems(input.lines, invoiceId, tenantId)
      await tx.insert(invoiceLines).values(mapped)
    }

    // Audit log
    await tx.insert(auditLog).values({
      tenantId,
      actorId,
      actorType: 'user',
      entityType: 'invoice',
      entityId: invoiceId,
      action: 'invoice.created_from_proposal',
      changes: null,
    })

    return { invoiceId, invoiceNumber: null }
  })
}
