/**
 * /v1/invoices routes — tenant-public-api (wave-11 leaf-D).
 *
 * GET  /v1/invoices           — list (paginated, filterable)
 * GET  /v1/invoices/:id       — get single with lines
 * POST /v1/invoices           — create draft
 * PATCH /v1/invoices/:id/status — trigger status transition
 */
import { Hono } from 'hono'
import { z } from 'zod'
import {
  listInvoices,
  getInvoiceWithLines,
  createInvoice,
  sendInvoice,
  approveInvoice,
  rejectInvoice,
  voidInvoice,
  getCustomerWithStats,
  getProjectById,
} from '@zync/db/queries'
import {
  hasScope,
  serializeInvoice,
  serializeInvoiceLine,
  errForbiddenScope,
  errNotFound,
  errValidation,
  errInternal,
} from '@zync/public-api'
import type { ApiKeyContext } from '../middleware/auth'
import type { Env } from '../env'
import { createDb } from '../db'

type AppEnv = { Bindings: Env }

export const invoicesRouter = new Hono<AppEnv>()

const listQuerySchema = z.object({
  limit: z.coerce.number().int().min(1).max(100).default(20),
  cursor: z.string().optional(),
  status: z.string().optional(),
  customer_id: z.string().uuid().optional(),
})

const createBodySchema = z.object({
  customer_id: z.string().uuid(),
  project_id: z.string().uuid().optional().nullable(),
  currency: z.string().min(1).max(3).optional(),
  due_date: z.string().optional().nullable(),
  notes: z.string().optional().nullable(),
  lines: z.array(z.object({
    description: z.string().min(1),
    quantity: z.number().min(0),
    unit_price: z.number().min(0),
    discount_pct: z.number().min(0).max(100).default(0),
    taxable: z.boolean().default(true),
    position: z.number().int().min(0),
  })).min(1),
})

const statusUpdateSchema = z.object({
  status: z.enum(['SENT', 'APPROVED', 'REJECTED', 'VOID']),
  reason: z.string().optional(),
  // sendInvoice needs issue_date and country_code
  issue_date: z.string().optional(),
  country_code: z.string().length(2).optional(),
})

invoicesRouter.get('/', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'invoices:read')) {
    return errForbiddenScope('invoices:read')
  }

  const parsed = listQuerySchema.safeParse(Object.fromEntries(new URL(c.req.url).searchParams))
  if (!parsed.success) {
    return errValidation('Invalid query parameters')
  }

  const db = createDb(c.env)
  const page = await listInvoices(db, apiKey.tenantId, {
    cursor: parsed.data.cursor,
    limit: parsed.data.limit,
    status: parsed.data.status as 'DRAFT' | 'SENT' | 'APPROVED' | 'REJECTED' | 'TAX_ISSUED' | 'PAID' | 'PARTIALLY_PAID' | 'VOID' | 'WRITTEN_OFF' | 'BAD_DEBT' | undefined,
    customerId: parsed.data.customer_id,
  })

  return c.json({
    data: page.items.map((row) =>
      serializeInvoice(
        {
          ...row,
          paidAt: row.paidAt ?? null,
          createdAt: row.createdAt,
          updatedAt: row.updatedAt,
        },
        [],
      ),
    ),
    meta: {
      total: page.total,
      next_cursor: page.nextCursor,
      has_more: page.nextCursor !== null,
      limit: parsed.data.limit,
    },
  })
})

invoicesRouter.get('/:id', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'invoices:read')) {
    return errForbiddenScope('invoices:read')
  }

  const db = createDb(c.env)
  const invoice = await getInvoiceWithLines(db, apiKey.tenantId, c.req.param('id'))
  if (!invoice) return errNotFound('Invoice')

  const lines = invoice.lines.map((l) =>
    serializeInvoiceLine({
      id: l.id,
      description: l.description,
      quantity: String(l.quantity),
      unitPrice: String(l.unitPrice),
      discountPct: String(l.discountPct),
      lineTotal: String(l.lineTotal),
      taxable: l.taxable,
      position: l.position,
    }),
  )

  return c.json({
    data: serializeInvoice(
      {
        ...invoice,
        paidAt: invoice.paidAt ?? null,
        createdAt: invoice.createdAt,
        updatedAt: invoice.updatedAt,
      },
      lines,
    ),
  })
})

invoicesRouter.post('/', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'invoices:write')) {
    return errForbiddenScope('invoices:write')
  }

  const body = await c.req.json().catch(() => null)
  const parsed = createBodySchema.safeParse(body)
  if (!parsed.success) {
    return errValidation(parsed.error.issues[0]?.message ?? 'Invalid body', parsed.error.issues[0]?.path[0] as string)
  }

  const db = createDb(c.env)

  const customer = await getCustomerWithStats(db, apiKey.tenantId, parsed.data.customer_id)
  if (!customer) return errNotFound('Customer')

  if (parsed.data.project_id) {
    const project = await getProjectById(db, apiKey.tenantId, parsed.data.project_id)
    if (!project) return errNotFound('Project')
  }

  // actorId: use key creator; countryCode: default IL for now (tenant country not available without extra query)
  const invoice = await createInvoice(db, apiKey.tenantId, apiKey.createdBy ?? 'api', {
    customerId: parsed.data.customer_id,
    projectId: parsed.data.project_id ?? null,
    currency: parsed.data.currency ?? 'ILS',
    dueDate: parsed.data.due_date ?? null,
    notes: parsed.data.notes ?? null,
    source: 'manual',
    lines: parsed.data.lines.map((l, i) => ({
      description: l.description,
      quantity: l.quantity,
      unitPrice: l.unit_price,
      discountPct: l.discount_pct,
      taxable: l.taxable,
      position: l.position ?? i,
    })),
  }, 'IL')

  const full = await getInvoiceWithLines(db, apiKey.tenantId, invoice.id)
  if (!full) return errInternal('Failed to retrieve created invoice')

  const lines = full.lines.map((l) =>
    serializeInvoiceLine({
      id: l.id,
      description: l.description,
      quantity: String(l.quantity),
      unitPrice: String(l.unitPrice),
      discountPct: String(l.discountPct),
      lineTotal: String(l.lineTotal),
      taxable: l.taxable,
      position: l.position,
    }),
  )

  return c.json({
    data: serializeInvoice(
      {
        ...full,
        paidAt: full.paidAt ?? null,
        createdAt: full.createdAt,
        updatedAt: full.updatedAt,
      },
      lines,
    ),
  }, 201)
})

invoicesRouter.patch('/:id/status', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'invoices:write')) {
    return errForbiddenScope('invoices:write')
  }

  const body = await c.req.json().catch(() => null)
  const parsed = statusUpdateSchema.safeParse(body)
  if (!parsed.success) {
    return errValidation('Invalid status value', 'status')
  }

  const db = createDb(c.env)
  const id = c.req.param('id')
  const actorId = apiKey.createdBy ?? 'api'

  try {
    switch (parsed.data.status) {
      case 'SENT':
        await sendInvoice(
          db, apiKey.tenantId, id, actorId,
          parsed.data.country_code ?? 'IL',
          parsed.data.issue_date ?? new Date().toISOString().slice(0, 10),
        )
        break
      case 'APPROVED':
        await approveInvoice(db, apiKey.tenantId, id, actorId)
        break
      case 'REJECTED':
        await rejectInvoice(db, apiKey.tenantId, id, actorId, parsed.data.reason ?? 'Rejected via API')
        break
      case 'VOID':
        await voidInvoice(db, apiKey.tenantId, id, actorId, parsed.data.reason ?? 'Voided via API')
        break
    }
  } catch (err) {
    console.error('invoice_status_patch_failed', {
      invoiceId: id,
      status: parsed.data.status,
      error: err instanceof Error ? err.message : String(err),
    })
    return errValidation('Cannot transition to this status', 'status')
  }

  const updated = await getInvoiceWithLines(db, apiKey.tenantId, id)
  if (!updated) return errNotFound('Invoice')

  const lines = updated.lines.map((l) =>
    serializeInvoiceLine({
      id: l.id,
      description: l.description,
      quantity: String(l.quantity),
      unitPrice: String(l.unitPrice),
      discountPct: String(l.discountPct),
      lineTotal: String(l.lineTotal),
      taxable: l.taxable,
      position: l.position,
    }),
  )

  return c.json({
    data: serializeInvoice(
      {
        ...updated,
        paidAt: updated.paidAt ?? null,
        createdAt: updated.createdAt,
        updatedAt: updated.updatedAt,
      },
      lines,
    ),
  })
})
