/**
 * Contract-to-invoice integration queries — contract-to-invoice (wave-9 leaf 2).
 *
 * generateInvoiceFromContract:
 *   - Reads the signed contract (status must be 'signed')
 *   - Requires contract.customerId (throws if null)
 *   - Idempotent: throws if metadata.invoiceId already set
 *   - Creates a DRAFT invoice with one placeholder line (quantity=1, unitPrice=0)
 *   - Stores invoiceId in contract.metadata in the same transaction
 *   - Inserts audit rows for both entities
 *
 * getContractInvoiceId: returns the linked invoiceId from metadata, or null.
 */
import { eq, and } from 'drizzle-orm'
import type { Db } from '../client'
import { contracts } from '../schema/contracts'
import { invoices, invoiceLines } from '../schema/invoices'
import { auditLog } from './_audit-forward'
import type { InvoiceWithLines, InvoiceLine, Invoice } from './invoices'

// ── helpers ────────────────────────────────────────────────────────────────────

function mapRow(row: typeof invoices.$inferSelect): Invoice {
  return {
    id: row.id,
    tenantId: row.tenantId,
    customerId: row.customerId ?? null,
    projectId: row.projectId ?? null,
    invoiceNumber: row.invoiceNumber ?? null,
    proformaNumber: row.proformaNumber ?? null,
    status: row.status as Invoice['status'],
    currency: row.currency,
    issueDate: row.issueDate ?? null,
    taxIssueDate: row.taxIssueDate ?? null,
    dueDate: row.dueDate ?? null,
    vatRate: row.vatRate ?? null,
    subtotal: String(row.subtotal),
    vatAmount: String(row.vatAmount),
    total: String(row.total),
    amountPaid: String(row.amountPaid ?? '0'),
    notes: row.notes ?? null,
    source: row.source,
    sentAt: row.sentAt ? row.sentAt.toISOString() : null,
    approvedAt: row.approvedAt ? row.approvedAt.toISOString() : null,
    approvedBy: row.approvedBy ?? null,
    approvalNote: row.approvalNote ?? null,
    rejectionReason: row.rejectionReason ?? null,
    rejectionNotifyCustomer: row.rejectionNotifyCustomer ?? false,
    taxIssuedAt: row.taxIssuedAt ? row.taxIssuedAt.toISOString() : null,
    paidAt: row.paidAt ? row.paidAt.toISOString() : null,
    externalId: row.externalId ?? null,
    externalProvider: row.externalProvider ?? null,
    voidReason: row.voidReason ?? null,
    voidedAt: row.voidedAt ? row.voidedAt.toISOString() : null,
    voidedBy: row.voidedBy ?? null,
    parentInvoiceId: row.parentInvoiceId ?? null,
    htmlSnapshotUrl: row.htmlSnapshotUrl ?? null,
    createdBy: row.createdBy,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
    customerName: null,
  }
}

function mapLineRow(row: typeof invoiceLines.$inferSelect): InvoiceLine {
  return {
    id: row.id,
    invoiceId: row.invoiceId,
    tenantId: row.tenantId,
    description: row.description,
    quantity: String(row.quantity),
    unitPrice: String(row.unitPrice),
    discountPct: String(row.discountPct),
    lineTotal: String(row.lineTotal),
    taxable: row.taxable,
    position: row.position,
    expenseId: row.expenseId ?? null,
    createdAt: row.createdAt.toISOString(),
  }
}

// ── generateInvoiceFromContract ────────────────────────────────────────────────

export async function generateInvoiceFromContract(
  db: Db,
  tenantId: string,
  userId: string,
  contractId: string,
): Promise<InvoiceWithLines> {
  return db.transaction(async (tx) => {
    // Fetch contract with row lock to prevent TOCTOU race on idempotency check
    const [contract] = await tx
      .select()
      .from(contracts)
      .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, contractId)))
      .limit(1)
      .for('update')

    if (!contract) throw new Error('Contract not found')
    if (contract.status !== 'signed') throw new Error('Contract must be signed to generate an invoice')
    if (!contract.customerId) throw new Error('Contract has no customer — add a customer before generating an invoice')

    // Idempotency: check existing link in metadata
    const meta = (contract.metadata ?? {}) as Record<string, unknown>
    if (meta.invoiceId && typeof meta.invoiceId === 'string') {
      throw new Error(`Invoice already exists: ${meta.invoiceId}`)
    }

    // Create DRAFT invoice
    const [invoice] = await tx
      .insert(invoices)
      .values({
        tenantId,
        customerId: contract.customerId,
        projectId: null,
        currency: 'ILS',
        dueDate: null,
        notes: null,
        source: 'manual',
        parentInvoiceId: null,
        status: 'DRAFT',
        subtotal: '0',
        vatAmount: '0',
        total: '0',
        createdBy: userId,
      })
      .returning()

    if (!invoice) throw new Error('Invoice insert failed')

    // Create placeholder line item
    const [line] = await tx
      .insert(invoiceLines)
      .values({
        invoiceId: invoice.id,
        tenantId,
        description: contract.title,
        quantity: '1',
        unitPrice: '0',
        discountPct: '0',
        lineTotal: '0',
        taxable: true,
        position: 0,
      })
      .returning()

    if (!line) throw new Error('Invoice line insert failed')

    // Link invoice to contract via metadata
    await tx
      .update(contracts)
      .set({
        metadata: { ...meta, invoiceId: invoice.id },
        updatedAt: new Date(),
      })
      .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, contractId)))

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

    // Audit: contract updated (invoice link)
    await tx.insert(auditLog).values({
      tenantId,
      actorId: userId,
      actorType: 'user',
      entityType: 'contract',
      entityId: contractId,
      action: 'contract.invoice_generated',
      changes: null,
    })

    return { ...mapRow(invoice), lines: [mapLineRow(line)] }
  })
}

// ── getContractInvoiceId ───────────────────────────────────────────────────────

export async function getContractInvoiceId(
  db: Db,
  tenantId: string,
  contractId: string,
): Promise<string | null> {
  const [row] = await db
    .select({ metadata: contracts.metadata })
    .from(contracts)
    .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, contractId)))
    .limit(1)

  if (!row) return null
  const meta = (row.metadata ?? {}) as Record<string, unknown>
  return typeof meta.invoiceId === 'string' ? meta.invoiceId : null
}
