/**
 * Proposal editor CRUD routes — proposal-editor (wave-11 leaf-C).
 * Mounted at /api/proposals in routes/index.ts (BEFORE the invoice-bridge /proposals routes).
 *
 * All routes: authMiddleware + requireModuleEnabled('marketing') + requirePermission.
 *
 * NOTE: /templates and /templates/:id MUST be registered before /:id to avoid
 * Hono treating the literal "templates" as a proposal ID.
 *
 * GET    /api/proposals                          — paginated list
 * POST   /api/proposals                          — create draft
 * GET    /api/proposal-templates                 — (mounted separately in routes/index.ts)
 * POST   /api/proposal-templates                 — (mounted separately in routes/index.ts)
 * DELETE /api/proposal-templates/:id             — (mounted separately in routes/index.ts)
 * GET    /api/proposals/:id                      — get full row
 * PATCH  /api/proposals/:id                      — update (DRAFT only)
 * DELETE /api/proposals/:id                      — delete (DRAFT only)
 * POST   /api/proposals/:id/send                 — send (DRAFT → SENT)
 * POST   /api/proposals/:id/extend               — reactivate EXPIRED proposal (wave-12)
 * GET    /api/proposals/:id/pdf                  — export PDF (proposal-pdf-export)
 */
import { Hono } from 'hono'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission } from '../middleware/guards'
import { requireModuleEnabled } from '../middleware/require-module-enabled'
import {
  listProposals,
  getProposal,
  createProposalDraft,
  updateProposalDraft,
  deleteProposal,
  sendProposalDraft,
  extendProposal,
  createProposalEditorSchema,
  updateProposalEditorSchema,
  getProposalSettings,
  getTenantById,
  getCustomerWithStats,
  getVatRate,
  assertTenantOwnsCustomer,
  invalidTenantReferenceBody,
} from '@zync/db/queries'
import { sendProposalSchema, extendProposalBodySchema, proposalContentSchema } from '@zync/types'
import { validateProposalContent } from '../lib/proposal-content-security'
import { validateSafeOutboundUrl } from '@zync/utils'
import { computeProposalTotals } from '../proposals/pdf-totals'
import { renderProposalHtml } from '../proposals/pdf-render'

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

export const proposalEditorRoute = new Hono<AppEnv>()

proposalEditorRoute.use('*', authMiddleware)

// ── GET /api/proposals ────────────────────────────────────────────────────────

proposalEditorRoute.get(
  '/',
  requireModuleEnabled('marketing'),
  requirePermission('marketing: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 status = url.searchParams.get('status') ?? undefined
    const customerId = url.searchParams.get('customer_id') ?? undefined
    const expiringSoon = url.searchParams.get('expiringSoon') === 'true'

    const db = c.get('db')
    const items = await listProposals(db, session.tid, { status, customerId, expiringSoon: expiringSoon || undefined })
    return c.json({ items }, 200)
  },
)

// ── POST /api/proposals ───────────────────────────────────────────────────────

proposalEditorRoute.post(
  '/',
  requireModuleEnabled('marketing'),
  requirePermission('marketing: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 = createProposalEditorSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
    }

    try {
      validateProposalContent(parsed.data.content)
    } catch (err) {
      const e = err as Error & { status?: number }
      return c.json({ error: e.message }, (e.status ?? 422) as 422)
    }

    const db = c.get('db')
    if (!(await assertTenantOwnsCustomer(db, session.tid, parsed.data.customer_id ?? null))) {
      return c.json(invalidTenantReferenceBody('customer_id'), 400)
    }
    const proposal = await createProposalDraft(db, session.tid, {
      ...parsed.data,
      createdBy: session.sub,
    })

    return c.json({ proposal }, 201)
  },
)

// ── GET /api/proposals/:id/pdf ────────────────────────────────────────────────
// proposal-pdf-export: generate and stream PDF for a proposal

const HTML_TO_PDF_URL = 'https://api.html-to-pdf.zync.is'

proposalEditorRoute.get(
  '/:id/pdf',
  requireModuleEnabled('marketing'),
  requirePermission('marketing:read'),
  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 = c.get('db')

    // Load proposal (tenant-scoped)
    const proposal = await getProposal(db, session.tid, id)
    if (!proposal) return c.json({ error: 'Not found' }, 404)

    // Parse content
    const contentParsed = proposalContentSchema.safeParse(proposal.content)
    if (!contentParsed.success) {
      return c.json({ error: 'Proposal has no valid content to export' }, 422)
    }
    const content = contentParsed.data

    // Load tenant
    const tenant = await getTenantById(db, session.tid)
    if (!tenant) return c.json({ error: 'Tenant not found' }, 404)

    // Load customer name
    let customerName = ''
    if (proposal.customerId) {
      const customerResult = await getCustomerWithStats(db, session.tid, proposal.customerId)
      if (customerResult) customerName = customerResult.customer.name
    }

    // VAT rate for tenant country + proposal created date
    const vatRate = await getVatRate(db, tenant.countryCode, new Date(proposal.createdAt))

    // Compute totals
    const totals = computeProposalTotals(content, vatRate)

    // Resolve tenant contact info from settings JSONB
    const tenantSettings = tenant.settings as Record<string, unknown> | null
    const contactEmail =
      typeof tenantSettings?.contactEmail === 'string' ? tenantSettings.contactEmail : null
    const phone =
      typeof tenantSettings?.phone === 'string' ? tenantSettings.phone : null

    // SSRF guard — reject private/internal logo URLs before embedding in PDF HTML.
    let safeLogoUrl: string | null = tenant.logoUrl ?? null
    if (safeLogoUrl) {
      const logoCheck = validateSafeOutboundUrl(safeLogoUrl)
      if (!logoCheck.ok) {
        return c.json({ error: 'Tenant logo URL must be a public HTTPS endpoint' }, 422)
      }
    }

    // Render HTML
    const html = renderProposalHtml({
      proposalName: proposal.name ?? proposal.title,
      customerName: customerName || 'Customer',
      createdAt: proposal.createdAt,
      expiresAt: proposal.expiresAt,
      content,
      totals,
      tenant: {
        name: tenant.name,
        contactEmail,
        phone,
        logoUrl: safeLogoUrl,
        locale: proposal.locale,
      },
    })

    // POST to html-to-pdf worker
    const pdfRes = await fetch(HTML_TO_PDF_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'text/html; charset=utf-8' },
      body: html,
    })

    if (!pdfRes.ok) {
      return c.json({ error: 'PDF generation failed' }, 502)
    }

    const pdfBytes = await pdfRes.arrayBuffer()

    // RFC 5987 filename for Hebrew/non-ASCII names
    const rawName = (proposal.name ?? proposal.title).replace(/[^\w\s-￿-]/g, '')
    const encodedName = encodeURIComponent(rawName)
    const disposition = `attachment; filename="proposal.pdf"; filename*=UTF-8''${encodedName}.pdf`

    return new Response(pdfBytes, {
      status: 200,
      headers: {
        'Content-Type': 'application/pdf',
        'Content-Disposition': disposition,
        'Cache-Control': 'no-store',
      },
    })
  },
)

// ── GET /api/proposals/:id ────────────────────────────────────────────────────

proposalEditorRoute.get(
  '/:id',
  requireModuleEnabled('marketing'),
  requirePermission('marketing:read'),
  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 = c.get('db')
    const proposal = await getProposal(db, session.tid, id)
    if (!proposal) return c.json({ error: 'Not found' }, 404)

    return c.json({ proposal }, 200)
  },
)

// ── PATCH /api/proposals/:id ──────────────────────────────────────────────────

proposalEditorRoute.patch(
  '/:id',
  requireModuleEnabled('marketing'),
  requirePermission('marketing: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 = updateProposalEditorSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
    }

    const db = c.get('db')
    const existing = await getProposal(db, session.tid, id)
    if (!existing) return c.json({ error: 'Not found' }, 404)
    if (existing.status !== 'draft') {
      return c.json({ error: 'Can only edit DRAFT proposals', status: existing.status }, 409)
    }

    if (parsed.data.content !== undefined) {
      try {
        validateProposalContent(parsed.data.content)
      } catch (err) {
        const e = err as Error & { status?: number }
        return c.json({ error: e.message }, (e.status ?? 422) as 422)
      }
    }

    const updated = await updateProposalDraft(db, session.tid, id, parsed.data)
    return c.json({ proposal: updated }, 200)
  },
)

// ── DELETE /api/proposals/:id ─────────────────────────────────────────────────

proposalEditorRoute.delete(
  '/:id',
  requireModuleEnabled('marketing'),
  requirePermission('marketing: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 = c.get('db')
    const existing = await getProposal(db, session.tid, id)
    if (!existing) return c.json({ error: 'Not found' }, 404)
    if (existing.status !== 'draft') {
      return c.json({ error: 'Can only delete DRAFT proposals', status: existing.status }, 409)
    }

    await deleteProposal(db, session.tid, id)
    return c.body(null, 204)
  },
)

// ── POST /api/proposals/:id/send ──────────────────────────────────────────────

proposalEditorRoute.post(
  '/:id/send',
  requireModuleEnabled('marketing'),
  requirePermission('marketing: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 = sendProposalSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
    }

    const db = c.get('db')
    const existing = await getProposal(db, session.tid, id)
    if (!existing) return c.json({ error: 'Not found' }, 404)
    if (existing.status !== 'draft') {
      return c.json({ error: 'Proposal already sent', status: existing.status }, 409)
    }

    const updated = await sendProposalDraft(db, session.tid, id)
    return c.json({ proposal: updated }, 200)
  },
)

// ── POST /api/proposals/:id/extend ────────────────────────────────────────────
// wave-12: reactivate an EXPIRED proposal with a new expiry date

proposalEditorRoute.post(
  '/:id/extend',
  requireModuleEnabled('marketing'),
  requirePermission('marketing: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 = extendProposalBodySchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
    }

    const db = c.get('db')

    // Resolve the new expiry date
    let newExpiresAt: Date | null = null
    if (parsed.data.expires_at) {
      newExpiresAt = new Date(parsed.data.expires_at)
    } else {
      // Fall back to tenant default
      const settings = await getProposalSettings(db, session.tid)
      const defaultDays = settings.proposal_default_valid_days
      if (defaultDays === null) {
        return c.json({ error: 'No expires_at provided and tenant has no default valid days configured' }, 422)
      }
      newExpiresAt = new Date(Date.now() + defaultDays * 24 * 60 * 60 * 1000)
    }

    try {
      const updated = await extendProposal(db, session.tid, id, newExpiresAt)
      return c.json({ proposal: updated }, 200)
    } catch (err) {
      if (err instanceof Error) {
        if (err.message === 'Proposal not found') return c.json({ error: 'Not found' }, 404)
        if (err.message === 'ProposalNotExpired') return c.json({ error: 'Proposal is not in EXPIRED status' }, 409)
      }
      throw err
    }
  },
)
