/**
 * Time-to-invoice bridge routes — time-to-invoice (P064).
 * Mounted at /api/time in apps/zync-api/src/routes/index.ts (sub-mount).
 *
 * Routes:
 *   GET  /unbilled   → list unbilled billable time entries (for TimeEntrySelector modal)
 *   POST /bill       → mark entries as billed within a transaction
 *                      (called by POST /api/invoices handler, not directly by UI)
 *
 * Auth: authMiddleware + requireModuleEnabled('time_management').
 * The GET /unbilled route is also used by the invoice creation form to populate
 * the TimeEntrySelector modal.
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../../types'
import { authMiddleware } from '../../middleware/auth'
import { requireModuleEnabled } from '../../middleware/require-module-enabled'
import { requirePermission } from '../../middleware/guards'
import {
  listUnbilledTimeEntries,
  markTimeEntriesBilled,
  findAlreadyBilledEntries,
  assertTenantOwnsInvoice,
  invalidTenantReferenceBody,
} from '@zync/db/queries'

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

const unbilledQuerySchema = z.object({
  projectId: z.string().uuid().optional(),
  userId: z.string().uuid().optional(),
  dateFrom: z
    .string()
    .regex(/^\d{4}-\d{2}-\d{2}$/, 'dateFrom must be YYYY-MM-DD')
    .optional(),
  dateTo: z
    .string()
    .regex(/^\d{4}-\d{2}-\d{2}$/, 'dateTo must be YYYY-MM-DD')
    .optional(),
  cursor: z.string().optional(),
  limit: z.coerce.number().int().min(1).max(200).optional(),
})

const billEntriesSchema = z.object({
  invoiceId: z.string().uuid(),
  entryIds: z.array(z.string().uuid()).min(1).max(500),
})

// ── Router ────────────────────────────────────────────────────────────────────

export const timeInvoiceRoutes = new Hono<AppEnv>()

timeInvoiceRoutes.use('*', authMiddleware)
timeInvoiceRoutes.use('*', requireModuleEnabled('time_management'))

// ── GET /api/time/unbilled ────────────────────────────────────────────────────

timeInvoiceRoutes.get(
  '/unbilled',
  requirePermission('time:read'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const url = new URL(c.req.url)
    const raw = Object.fromEntries(url.searchParams.entries())
    const parsed = unbilledQuerySchema.safeParse(raw)
    if (!parsed.success) {
      return c.json({ error: 'Invalid query parameters', issues: parsed.error.issues }, 400)
    }

    const { projectId, userId, dateFrom, dateTo, cursor, limit } = parsed.data

    // Non-admin users can only see their own entries unless they have time:read_all
    const targetUserId =
      userId && userId !== session.sub
        ? session.permissions?.includes('time:read_all')
          ? userId
          : null // forbidden
        : userId

    if (userId && userId !== session.sub && targetUserId === null) {
      return c.json({ error: 'Forbidden — requires time:read_all to filter by other users' }, 403)
    }

    const db = c.get('db')
    const result = await listUnbilledTimeEntries(db, {
      tenantId: session.tid,
      projectId,
      userId: targetUserId ?? undefined,
      dateFrom,
      dateTo,
      cursor,
      limit,
    })

    return c.json(result, 200)
  },
)

// ── POST /api/time/bill ───────────────────────────────────────────────────────
//
// Internal endpoint: called from the POST /api/invoices handler (or from tests)
// to mark a batch of entries as billed in the same database transaction.
//
// The invoice creation flow:
//   1. POST /api/invoices body includes billedEntryIds?: string[]
//   2. That route creates the invoice record
//   3. Calls POST /api/time/bill (or directly calls markTimeEntriesBilled in the
//      same db.transaction() — preferred since it avoids an HTTP round-trip)
//
// This route is provided as a convenience endpoint for scenarios where the
// invoices-core route needs to delegate to this module without importing from it.

timeInvoiceRoutes.post(
  '/bill',
  requirePermission('invoices:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const body = await c.req.json().catch(() => null)
    const parsed = billEntriesSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)
    }

    const { invoiceId, entryIds } = parsed.data
    const db = c.get('db')

    if (!(await assertTenantOwnsInvoice(db, session.tid, invoiceId))) {
      return c.json(invalidTenantReferenceBody('invoice_id'), 400)
    }

    // Pre-flight: check for already-billed entries
    const alreadyBilled = await findAlreadyBilledEntries(db, session.tid, entryIds)
    if (alreadyBilled.length > 0) {
      return c.json(
        {
          error: 'Some entries are already billed',
          alreadyBilledIds: alreadyBilled,
        },
        409,
      )
    }

    const now = new Date()
    await db.transaction(async (tx) => {
      await markTimeEntriesBilled(tx, session.tid!, {
        entryIds,
        invoiceId,
        billedAt: now,
      })
    })

    return c.json({ ok: true, billedCount: entryIds.length }, 200)
  },
)
