/**
 * Public contract signing routes — contract-signing-page (wave-11 leaf-C).
 * Mounted at /api/sign in apps/zync-api/src/index.ts (no auth middleware).
 *
 * Rate-limited: 20 req/min per IP+token via RATE_LIMITER_SIGN.
 *
 * GET    /api/sign/:token          — fetch signing page data (resolves by signatory token)
 * POST   /api/sign/:token          — submit signature
 * POST   /api/sign/:token/decline  — decline to sign
 * GET    /api/sign/:token/pdf      — 302 → short-TTL signed R2 URL for signed PDF
 */
import { Hono } from 'hono'
import { z } from 'zod'
import {
  createDb,
  getSignatoryByToken,
  signContractByToken,
  declineContract,
  recordContractView,
  recordContractAuditEvent,
  getTenantById,
  getWhiteLabelConfig,
  listSignatories,
} from '@zync/db/queries'
import type { AppEnv } from '../types'
import type { SignPageData, TenantId } from '@zync/types'
import { signKbUrl } from '../lib/kb-storage'
import { currentTurnOrder, isSequential, isSignatoryTurn } from '../contracts/signing-order'

// ── Validation ────────────────────────────────────────────────────────────────

const signSubmitSchema = z.object({
  signature_data: z.string().min(10),
  signature_type: z.enum(['drawn', 'typed']),
  name: z.string().min(1).max(255),
  email: z.string().email(),
})

const declineSchema = z.object({
  reason: z.string().max(1000).optional(),
})

// ── Rate limit helper ─────────────────────────────────────────────────────────

async function checkSignRateLimit(
  env: AppEnv['Bindings'],
  ip: string,
  token: string,
): Promise<boolean> {
  const { success } = await (async () => { try { const _r = await env.RATE_LIMITER_SIGN?.limit({ key: `${ip}:${token}` }); return _r ?? { success: true }; } catch { return { success: true }; } })()
  return success
}

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

export const signRoute = new Hono<AppEnv>()

function toSignContractStatus(status: string): SignPageData['contract']['status'] {
  switch (status.toLowerCase()) {
    case 'draft':
      return 'DRAFT'
    case 'sent':
      return 'SENT'
    case 'viewed':
      return 'VIEWED'
    case 'signed':
      return 'SIGNED'
    case 'voided':
      return 'VOIDED'
    default:
      return 'SENT'
  }
}

// ── GET /api/sign/:token ──────────────────────────────────────────────────────

signRoute.get('/:token', async (c) => {
  const token = c.req.param('token')
  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('x-forwarded-for') ?? 'unknown'

  const allowed = await checkSignRateLimit(c.env, ip, token)
  if (!allowed) return c.json({ error: 'rate_limited' }, 429, { 'Retry-After': '60' })

  const db = createDb(c.env)
  const row = await getSignatoryByToken(db, token)
  if (!row) return c.json({ error: 'not_found' }, 404)

  // Log view event (non-blocking)
  void recordContractView(
    db,
    row.contractId,
    row.tenantId,
    row.email,
    ip,
    c.req.header('User-Agent') ?? null,
  )

  // Branding
  const [tenant, wl] = await Promise.all([
    getTenantById(db, row.tenantId as unknown as TenantId),
    getWhiteLabelConfig(db, row.tenantId),
  ])
  const allSignatories = await listSignatories(db, row.contractId, row.tenantId)
  const orderRows = allSignatories.map((s) => ({
    id: s.id,
    order: s.order,
    signedAt: s.signedAt,
    declinedAt: s.declinedAt,
  }))
  const waitingFor =
    isSequential(orderRows) && row.signedAt === null && row.declinedAt === null && row.order !== currentTurnOrder(orderRows)
      ? allSignatories.find(
          (s) =>
            s.order === currentTurnOrder(orderRows) &&
            s.signedAt === null &&
            s.declinedAt === null,
        )
      : null
  const tenantLocale =
    tenant &&
    typeof tenant === 'object' &&
    'settings' in tenant &&
    tenant.settings &&
    typeof tenant.settings === 'object' &&
    'locale' in tenant.settings &&
    (tenant.settings.locale === 'he' || tenant.settings.locale === 'en')
      ? tenant.settings.locale
      : null

  const data: SignPageData = {
    contract: {
      title: row.contract.title,
      content: row.contract.content,
      signingDeadline: row.tokenExpiresAt ?? '',
      status: toSignContractStatus(row.contract.status),
      signedPdfReady: Boolean(row.contract.signedPdfR2Key),
    },
    signatory: {
      name: row.name ?? '',
      email: row.email,
      order: row.order,
      signedAt: row.signedAt,
      declinedAt: row.declinedAt,
      declinedReason: row.declinedReason,
    },
    waitingFor: waitingFor ? { name: waitingFor.name ?? waitingFor.email } : undefined,
    tenantBranding: {
      logoR2Key: wl?.logoUrl ?? null,
      primaryColor: wl?.primaryColor ?? null,
      tenantName: tenant?.name ?? 'Unknown',
      customDomain: !!(wl?.customDomain && wl.sslStatus === 'active'),
      locale: tenantLocale,
    },
  }

  return c.json(data, 200)
})

// ── POST /api/sign/:token ─────────────────────────────────────────────────────

signRoute.post('/:token', async (c) => {
  const token = c.req.param('token')
  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('x-forwarded-for') ?? 'unknown'

  const allowed = await checkSignRateLimit(c.env, ip, token)
  if (!allowed) return c.json({ error: 'rate_limited' }, 429, { 'Retry-After': '60' })

  const body = await c.req.json().catch(() => null)
  const parsed = signSubmitSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 422)
  }

  const db = createDb(c.env)
  const row = await getSignatoryByToken(db, token)
  if (!row) return c.json({ error: 'not_found' }, 404)

  if (row.tokenExpiresAt && new Date(row.tokenExpiresAt) < new Date()) {
    return c.json({ error: 'expired' }, 410)
  }
  if (row.contract.status === 'voided') {
    return c.json({ error: 'voided' }, 410)
  }

  const allSignatories = await listSignatories(db, row.contractId, row.tenantId)
  const orderRows = allSignatories.map((s) => ({
    id: s.id,
    order: s.order,
    signedAt: s.signedAt,
    declinedAt: s.declinedAt,
  }))
  const thisSignatory = orderRows.find((s) => s.id === row.signatoryId)
  if (!thisSignatory || !isSignatoryTurn(thisSignatory, orderRows)) {
    return c.json({ error: 'not_your_turn' }, 409)
  }

  const { ok, alreadySigned } = await signContractByToken(db, {
    signatoryId: row.signatoryId,
    signatureData: parsed.data.signature_data,
    signatureType: parsed.data.signature_type,
    signerIp: ip,
    signerUserAgent: c.req.header('User-Agent') ?? null,
    fullName: parsed.data.name,
    email: parsed.data.email,
  })

  if (!ok) return c.json({ error: 'Failed to sign' }, 500)
  if (alreadySigned) return c.json({ error: 'already_signed', status: 409 }, 409)

  return c.json({ ok: true, signedAt: new Date().toISOString() }, 200)
})

// ── POST /api/sign/:token/decline ─────────────────────────────────────────────

signRoute.post('/:token/decline', async (c) => {
  const token = c.req.param('token')
  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('x-forwarded-for') ?? 'unknown'

  const allowed = await checkSignRateLimit(c.env, ip, token)
  if (!allowed) return c.json({ error: 'rate_limited' }, 429, { 'Retry-After': '60' })

  const body = await c.req.json().catch(() => null)
  const parsed = declineSchema.safeParse(body ?? {})
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }

  const db = createDb(c.env)
  const row = await getSignatoryByToken(db, token)
  if (!row) return c.json({ error: 'not_found' }, 404)

  if (row.tokenExpiresAt && new Date(row.tokenExpiresAt) < new Date()) {
    return c.json({ error: 'expired' }, 410)
  }

  const { ok, alreadyDone } = await declineContract(
    db,
    row.signatoryId,
    row.contractId,
    row.tenantId,
    row.email,
    parsed.data.reason ?? null,
    ip,
    c.req.header('User-Agent') ?? null,
  )

  if (!ok) return c.json({ error: 'Failed to decline' }, 500)
  if (alreadyDone) return c.json({ declined: true, alreadyDone: true }, 200)

  return c.json({ declined: true }, 200)
})

// ── GET /api/sign/:token/pdf ──────────────────────────────────────────────────

signRoute.get('/:token/pdf', async (c) => {
  const token = c.req.param('token')
  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('x-forwarded-for') ?? 'unknown'

  const allowed = await checkSignRateLimit(c.env, ip, token)
  if (!allowed) return c.json({ error: 'rate_limited' }, 429, { 'Retry-After': '60' })

  const db = createDb(c.env)
  const row = await getSignatoryByToken(db, token)
  if (!row) return c.json({ error: 'not_found' }, 404)

  if (row.tokenExpiresAt && new Date(row.tokenExpiresAt) < new Date()) {
    return c.json({ error: 'expired' }, 410)
  }

  if (!row.contract.signedPdfR2Key) {
    return c.json({ error: 'pdf_not_ready' }, 409)
  }

  // 15-minute signed URL for signed PDF download
  const signedUrl = await signKbUrl(c.env, row.contract.signedPdfR2Key, 15 * 60)

  // Audit log download event (non-blocking)
  void recordContractAuditEvent(db, {
    contractId: row.contractId,
    tenantId: row.tenantId,
    event: 'downloaded',
    actorType: 'signatory',
    actorId: row.email,
    ipAddress: ip,
    userAgent: c.req.header('User-Agent') ?? null,
  }).catch(() => { /* non-critical */ })

  return c.redirect(signedUrl, 302)
})
