/**
 * Payment reconciliation routes — payment-reconciliation (wave-12).
 * Mounted in apps/zync-api/src/routes/index.ts.
 *
 * Routes:
 *   GET    /api/invoices/outstanding            → PaginatedResponse<OutstandingInvoice>
 *   GET    /api/reconcile/unmatched             → { items: UnmatchedPaymentObject[] }
 *   POST   /api/reconcile/unmatched             → 201 UnmatchedPaymentObject
 *   DELETE /api/reconcile/unmatched/:id         → 204 | 409 already_matched | 404
 *   PATCH  /api/reconcile/unmatched/:id/match   → 200 { payment, unmatched }
 *
 * All routes guarded by authMiddleware + requirePermission('invoices:write').
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission } from '../middleware/guards'
import {
  createDb,
  listOutstandingInvoices,
  listUnmatchedPayments,
  createUnmatchedPayment,
  deleteUnmatchedPayment,
  matchUnmatchedPayment,
  AlreadyMatchedError,
  InvalidInvoiceStatusError,
} from '@zync/db/queries'

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

const paymentMethodEnum = z.enum(['bank_transfer', 'credit_card', 'check', 'cash', 'other'])
const currencyEnum = z.enum(['ILS', 'USD', 'EUR'])

const createUnmatchedPaymentSchema = z.object({
  amount: z.string().regex(/^\d+(\.\d{1,4})?$/, 'amount must be a positive decimal'),
  currency: currencyEnum.optional(),
  paid_at: z.string().date(),
  payment_method: paymentMethodEnum,
  reference: z.string().max(200).optional().nullable(),
  payer_name: z.string().max(200).optional().nullable(),
  notes: z.string().max(2000).optional().nullable(),
})

const outstandingQuerySchema = z.object({
  search: z.string().optional(),
  sort: z.enum(['oldest_due', 'newest_due', 'amount', 'customer']).default('oldest_due'),
  page: z.coerce.number().int().min(1).default(1),
  per_page: z.coerce.number().int().min(1).max(100).default(25),
})

const matchSchema = z.object({
  invoice_id: z.string().uuid(),
})

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

export const outstandingInvoicesRoute = new Hono<AppEnv>()
outstandingInvoicesRoute.use('*', authMiddleware)

// GET /api/invoices/outstanding
outstandingInvoicesRoute.get(
  '/outstanding',
  requirePermission('invoices:write'),
  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 = outstandingQuerySchema.safeParse(raw)
    if (!parsed.success) {
      return c.json({ error: 'Invalid query parameters', issues: parsed.error.issues }, 400)
    }

    const db = createDb(c.env)
    const result = await listOutstandingInvoices(db, session.tid, {
      search: parsed.data.search,
      sort: parsed.data.sort,
      page: parsed.data.page,
      perPage: parsed.data.per_page,
    })

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

export const reconcileRoute = new Hono<AppEnv>()
reconcileRoute.use('*', authMiddleware)

// GET /api/reconcile/unmatched
reconcileRoute.get(
  '/unmatched',
  requirePermission('invoices:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const db = createDb(c.env)
    const items = await listUnmatchedPayments(db, session.tid)
    return c.json({ items }, 200)
  },
)

// POST /api/reconcile/unmatched
reconcileRoute.post(
  '/unmatched',
  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 = createUnmatchedPaymentSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)
    }

    const db = createDb(c.env)
    const item = await createUnmatchedPayment(db, session.tid, {
      amount: parsed.data.amount,
      currency: parsed.data.currency,
      paidAt: parsed.data.paid_at,
      paymentMethod: parsed.data.payment_method,
      reference: parsed.data.reference ?? null,
      payerName: parsed.data.payer_name ?? null,
      notes: parsed.data.notes ?? null,
    })

    return c.json(item, 201)
  },
)

// DELETE /api/reconcile/unmatched/:id
reconcileRoute.delete(
  '/unmatched/:id',
  requirePermission('invoices:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const id = c.req.param('id')
    const db = createDb(c.env)

    try {
      await deleteUnmatchedPayment(db, session.tid, id)
      return c.body(null, 204)
    } catch (err) {
      if (err instanceof AlreadyMatchedError) {
        return c.json({ error: 'already_matched', message: err.message }, 409)
      }
      if (err instanceof Error && err.message === 'not_found') {
        return c.json({ error: 'not_found', message: 'Unmatched payment not found' }, 404)
      }
      throw err
    }
  },
)

// PATCH /api/reconcile/unmatched/:id/match
reconcileRoute.patch(
  '/unmatched/:id/match',
  requirePermission('invoices:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

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

    const db = createDb(c.env)

    try {
      const result = await matchUnmatchedPayment(
        db,
        session.tid,
        id,
        parsed.data.invoice_id,
        session.sub,
      )
      return c.json(result, 200)
    } catch (err) {
      if (err instanceof AlreadyMatchedError) {
        return c.json({ error: 'already_matched', message: err.message }, 409)
      }
      if (err instanceof InvalidInvoiceStatusError) {
        return c.json({ error: 'invalid_invoice_status', message: err.message }, 422)
      }
      if (err instanceof Error && err.message === 'not_found') {
        return c.json({ error: 'not_found', message: 'Unmatched payment not found' }, 404)
      }
      if (err instanceof Error && err.message === 'invoice_not_found') {
        return c.json({ error: 'not_found', message: 'Invoice not found' }, 404)
      }
      throw err
    }
  },
)
