/**
 * Public proposal routes — public-proposal-view (wave-11 leaf-C).
 *
 * No auth. Rate-limited by RATE_LIMITER_PROPOSAL (10 req/min per IP+token).
 *
 * GET    /api/proposals/:token/public    — view proposal, log view event, return branding
 * POST   /api/proposals/:token/accept    — accept proposal (idempotent)
 * POST   /api/proposals/:token/reject    — reject proposal (idempotent)
 */
import { Hono } from 'hono'
import type { Context } from 'hono'
import { z } from 'zod'
import {
  createDb,
  getProposalByPublicToken,
  recordProposalView,
  acceptProposalByToken,
  rejectProposalByToken,
  getTenantById,
  getWhiteLabelConfig,
} from '@zync/db/queries'
import type { AppEnv } from '../types'
import type { TenantId } from '@zync/types'

// ── Schemas ───────────────────────────────────────────────────────────────────

const acceptSchema = z.object({
  name: z.string().min(1).max(255).optional(),
})

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

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

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

// ── Branding helper ───────────────────────────────────────────────────────────

async function getBranding(
  db: ReturnType<typeof createDb>,
  tenantId: string,
) {
  const [tenant, wl] = await Promise.all([
    getTenantById(db, tenantId as unknown as TenantId),
    getWhiteLabelConfig(db, tenantId),
  ])
  return {
    logoR2Key: wl?.logoUrl ?? null,
    primaryColor: wl?.primaryColor ?? null,
    tenantName: tenant?.name ?? 'Unknown',
    customDomain: wl?.customDomain ?? null,
    defaultLocale: ((tenant?.settings as Record<string, unknown> | null)?.locale as string) ?? 'he',
  }
}

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

export const proposalsPublicRoute = new Hono<AppEnv>()

function getTokenOrResponse(c: Context<AppEnv>) {
  const token = c.req.param('token')
  if (!token) {
    return { response: c.body(null, 404) }
  }
  return { token }
}

function proposalContentRecipientName(content: unknown): string | null {
  if (!content || typeof content !== 'object') return null
  const recipientName = (content as { recipientName?: unknown }).recipientName
  return typeof recipientName === 'string' && recipientName.trim().length > 0
    ? recipientName.trim()
    : null
}

function resolveTrackingUrl(c: Context<AppEnv>): URL {
  const forwarded = c.req.header('x-original-url')
  if (forwarded) {
    try {
      return new URL(forwarded)
    } catch {
      // Fall back to the route URL when the forwarded value is malformed.
    }
  }
  return new URL(c.req.url)
}

async function handleProposalPublicView(c: Context<AppEnv>) {
  const tokenResult = getTokenOrResponse(c)
  if ('response' in tokenResult) {
    return tokenResult.response
  }
  const { token } = tokenResult
  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('x-forwarded-for') ?? 'unknown'

  const allowed = await checkProposalRateLimit(c.env, token, ip)
  if (!allowed) return c.json({ error: 'rate_limited' }, 429)

  const db = createDb(c.env)
  const proposal = await getProposalByPublicToken(db, token)
  // Drafts must not be publicly accessible — only sent/viewed/accepted proposals
  if (!proposal || proposal.status === 'draft') return c.body(null, 404)

  const url = resolveTrackingUrl(c)
  const viewData = {
    ip,
    userAgent: c.req.header('User-Agent') ?? null,
    referrer: c.req.header('Referer') ?? null,
    utmSource: url.searchParams.get('utm_source'),
    utmMedium: url.searchParams.get('utm_medium'),
    utmCampaign: url.searchParams.get('utm_campaign'),
  }

  await recordProposalView(db, proposal.id, proposal.tenantId, viewData)

  const branding = await getBranding(db, proposal.tenantId)

  // Analytics Engine: proposal_view
  c.env.ANALYTICS_ENGINE.writeDataPoint({
    blobs: [
      proposal.tenantId,
      proposal.id,
      proposal.leadId ?? '',
      '',                           // catalogShareId = null
      viewData.utmSource ?? '',
      viewData.utmMedium ?? '',
      viewData.utmCampaign ?? '',
    ],
    indexes: [proposal.tenantId],
  })

  // First-view notification fires via QUEUE (webhook.deliver) — not in this path for leaf-C.

  return c.json({
    proposal: {
      name: proposal.name ?? proposal.title ?? '',
      content: proposal.content,
      status: (proposal.status as string).toUpperCase(),
      expiresAt: proposal.expiresAt,
      viewCount: proposal.viewCount,
      sentAt: proposal.sentAt ?? null,
      acceptedAt: proposal.acceptedAt ?? null,
      acceptedByName: proposal.acceptedByName ?? null,
      rejectedAt: proposal.rejectedAt ?? null,
      locale: (proposal.locale ?? 'he') as 'he' | 'en',
    },
    tenantBranding: {
      logoR2Key: branding.logoR2Key,
      primaryColor: branding.primaryColor,
      tenantName: branding.tenantName,
      customDomain: branding.customDomain,
      defaultLocale: branding.defaultLocale as 'he' | 'en',
    },
  }, 200)
}

// ── GET /api/proposals/:token/public ─────────────────────────────────────────
proposalsPublicRoute.get('/:token/public', handleProposalPublicView)
proposalsPublicRoute.get('/:token', handleProposalPublicView)

// ── POST /api/proposals/:token/accept ─────────────────────────────────────────

proposalsPublicRoute.post('/:token/accept', async (c) => {
  const tokenResult = getTokenOrResponse(c)
  if ('response' in tokenResult) {
    return tokenResult.response
  }
  const { token } = tokenResult
  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('x-forwarded-for') ?? 'unknown'

  const allowed = await checkProposalRateLimit(c.env, token, ip)
  if (!allowed) return c.json({ error: 'rate_limited' }, 429)

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

  const db = createDb(c.env)
  const proposal = await getProposalByPublicToken(db, token)
  if (!proposal || proposal.status === 'draft') return c.body(null, 404)

  const acceptedByName = parsed.data.name?.trim() || proposalContentRecipientName(proposal.content)
  const result = await acceptProposalByToken(db, proposal.id, proposal.tenantId, { acceptedByName })
  if (!result.ok) {
    return c.json({ status: result.status.toUpperCase() }, 409)
  }

  // Analytics Engine: proposal_accepted
  c.env.ANALYTICS_ENGINE.writeDataPoint({
    blobs: [proposal.tenantId, proposal.id, proposal.leadId ?? '', '', '', '', ''],
    indexes: [proposal.tenantId],
  })

  return c.json({
    status: 'ACCEPTED',
    acceptedAt: new Date().toISOString(),
    acceptedByName: acceptedByName ?? null,
  }, 200)
})

// ── POST /api/proposals/:token/reject ─────────────────────────────────────────

proposalsPublicRoute.post('/:token/reject', async (c) => {
  const tokenResult = getTokenOrResponse(c)
  if ('response' in tokenResult) {
    return tokenResult.response
  }
  const { token } = tokenResult
  const ip = c.req.header('CF-Connecting-IP') ?? c.req.header('x-forwarded-for') ?? 'unknown'

  const allowed = await checkProposalRateLimit(c.env, token, ip)
  if (!allowed) return c.json({ error: 'rate_limited' }, 429)

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

  const db = createDb(c.env)
  const proposal = await getProposalByPublicToken(db, token)
  if (!proposal || proposal.status === 'draft') return c.body(null, 404)

  const result = await rejectProposalByToken(db, proposal.id, proposal.tenantId, {
    reason: parsed.data.reason?.trim() || null,
  })
  if (!result.ok) {
    return c.json({ status: result.status.toUpperCase() }, 409)
  }

  return c.json({
    status: 'REJECTED',
    rejectedAt: new Date().toISOString(),
  }, 200)
})
