/**
 * Leads routes — marketing-leads-pipeline.
 * Mounted at /api/marketing/leads (behind authMiddleware in router.ts).
 *
 * GET    /                    list leads (filterable, cursor-paginated)
 * POST   /                    create lead
 * GET    /pipeline            kanban board data (leads grouped by stage + stage list)
 * GET    /stages              list pipeline stages
 * POST   /stages              create pipeline stage
 * PATCH  /stages/:stageId     update pipeline stage
 * DELETE /stages/:stageId     delete pipeline stage (non-system only)
 * GET    /:id                 lead detail + activities
 * PATCH  /:id                 update lead fields
 * PATCH  /:id/move            move lead to stage/position (drag-drop)
 * DELETE /:id                 archive lead (soft-delete)
 * POST   /:id/convert         convert WON lead to customer
 * POST   /:id/create-proposal create draft proposal from lead data (wave-10 leaf6)
 * GET    /:id/activities      paginated activity feed
 * POST   /:id/activities      add manual activity (note, call, email)
 *
 * wave-14: lead-qualification-scoring
 * POST   /:id/score           recalculate score synchronously
 * GET    /:id/score           read score + breakdown
 *
 * wave-14: lead-lost-re-engagement
 * PATCH  /:id/reopen          reopen LOST lead → NEW (or specified stage)
 * GET    /lost-reasons        get tenant lost reason labels
 */
import { Hono } from 'hono'
import type { AppEnv } from '../../types'
import { requirePermission } from '../../middleware/guards'
import { leadScoreRoute } from './lead-score'
import {
  listLeads,
  getLead,
  getLeadDetail,
  createLead,
  updateLead,
  moveLead,
  archiveLead,
  listLeadActivities,
  addLeadActivity,
  listLinkedProposals,
  listLinkedContracts,
  listLinkedInvoices,
  listLinkedTasks,
  listPipelineStages,
  createPipelineStage,
  updatePipelineStage,
  deletePipelineStage,
  createLeadSchema,
  updateLeadSchema,
  moveLeadSchema,
  convertLeadSchema,
  leadFiltersSchema,
  addLeadActivitySchema,
  createPipelineStageSchema,
  updatePipelineStageSchema,
  convertLeadToCustomer,
  createProposalFromLead,
  logAuditEvent,
  reopenLead,
  getLostReasons,
  assertActiveTenantAssignee,
  invalidTenantReferenceBody,
} from '@zync/db/queries'
import { reopenLeadSchema } from '@zync/types'
import {
  applyFieldPermissions,
  applyFieldPermissionsToPage,
  getFieldPermissionContext,
} from '../field-permissions/enforcement'

export const leadsRoute = new Hono<AppEnv>()

// ── Pipeline stages ───────────────────────────────────────────────────────────

// GET /api/marketing/leads/stages
leadsRoute.get('/stages', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')

  const stages = await listPipelineStages(db, session.tid)
  return c.json({ stages })
})

// POST /api/marketing/leads/stages
leadsRoute.post(
  '/stages',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')

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

    const stage = await createPipelineStage(db, session.tid, parsed.data)
    return c.json({ stage }, 201)
  },
)

// PATCH /api/marketing/leads/stages/:stageId
leadsRoute.patch(
  '/stages/:stageId',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')
    const { stageId } = c.req.param()

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

    try {
      const stage = await updatePipelineStage(db, session.tid, stageId, parsed.data)
      return c.json({ stage })
    } catch (err) {
      if (err instanceof Error && err.message === 'Pipeline stage not found') {
        return c.json({ error: 'Not found' }, 404)
      }
      throw err
    }
  },
)

// DELETE /api/marketing/leads/stages/:stageId
leadsRoute.delete('/stages/:stageId', requirePermission('marketing:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { stageId } = c.req.param()

  try {
    await deletePipelineStage(db, session.tid, stageId)
    return c.json({ ok: true })
  } catch (err) {
    if (err instanceof Error) {
      if (err.message === 'Stage not found') return c.json({ error: 'Not found' }, 404)
      if (err.message === 'System pipeline stages cannot be deleted') {
        return c.json({ error: 'System stages cannot be deleted' }, 422)
      }
    }
    throw err
  }
})

// ── Kanban board data ─────────────────────────────────────────────────────────

// GET /api/marketing/leads/pipeline
leadsRoute.get('/pipeline', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')

  const includeLost = c.req.query('include_lost') === 'true'

  const [stages, { items: leadsData }] = await Promise.all([
    listPipelineStages(db, session.tid),
    listLeads(db, session.tid, {
      include_lost: includeLost,
      include_archived: false,
      limit: 100,
      sort_by: 'created_at',
      sort_dir: 'desc',
    }),
  ])

  // Group leads by stage
  const columnMap: Record<string, typeof leadsData> = {}
  for (const stage of stages) {
    columnMap[stage.slug] = []
  }
  for (const lead of leadsData) {
    if (!columnMap[lead.stage]) columnMap[lead.stage] = []
    columnMap[lead.stage]!.push(lead)
  }
  // Sort each column by stagePosition
  for (const key of Object.keys(columnMap)) {
    columnMap[key]!.sort((a, b) => Number(a.stagePosition) - Number(b.stagePosition))
  }

  const { role, rules } = await getFieldPermissionContext(db, session)
  for (const key of Object.keys(columnMap)) {
    columnMap[key] = columnMap[key]!.map((lead) => applyFieldPermissions(lead, 'lead', role, rules).data)
  }

  return c.json({ stages, columns: columnMap })
})

// ── Leads list / create ───────────────────────────────────────────────────────

// GET /api/marketing/leads
leadsRoute.get(
  '/',
  requirePermission('marketing:read'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')

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

    const result = await listLeads(db, session.tid, parsed.data)
    const { role, rules } = await getFieldPermissionContext(db, session)
    return c.json(applyFieldPermissionsToPage(result, 'lead', role, rules))
  },
)

// POST /api/marketing/leads
leadsRoute.post(
  '/',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')

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

    if (!(await assertActiveTenantAssignee(db, session.tid, parsed.data.assigned_to))) {
      return c.json(invalidTenantReferenceBody('assigned_to'), 400)
    }

    const lead = await createLead(db, session.tid, parsed.data)

    // Log creation activity
    await addLeadActivity(db, session.tid, lead.id, session.sub, {
      type: 'note',
      content: 'Lead created',
      metadata: { source: parsed.data.source ?? 'manual' },
    })

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

// ── Lead detail ───────────────────────────────────────────────────────────────

// GET /api/marketing/leads/:id
leadsRoute.get('/:id', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

  const lead = await getLeadDetail(db, session.tid, id)
  if (!lead) return c.json({ error: 'Not found' }, 404)

  const { items: activities } = await listLeadActivities(db, session.tid, id, 20)
  const { role, rules } = await getFieldPermissionContext(db, session)
  const filtered = applyFieldPermissions(lead, 'lead', role, rules)
  return c.json({
    lead: filtered.data,
    activities,
    _meta: { readOnly: filtered.readOnly },
  })
})

// PATCH /api/marketing/leads/:id
leadsRoute.patch(
  '/:id',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')
    const { id } = c.req.param()

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

    const existing = await getLead(db, session.tid, id)
    if (!existing) return c.json({ error: 'Not found' }, 404)

    if (
      input.assigned_to !== undefined &&
      !(await assertActiveTenantAssignee(db, session.tid, input.assigned_to))
    ) {
      return c.json(invalidTenantReferenceBody('assigned_to'), 400)
    }

    const lead = await updateLead(db, session.tid, id, input)

    // Log stage change activity + audit + webhook if stage changed
    if (input.stage && input.stage !== existing.stage) {
      await addLeadActivity(db, session.tid, id, session.sub, {
        type: 'stage_changed',
        metadata: {
          from: existing.stage,
          to: input.stage,
          ...(input.lost_reason ? { reason: input.lost_reason } : {}),
        },
      })
      await logAuditEvent(c, {
        tenantId: session.tid,
        userId: session.sub,
        eventType: 'lead.stage_updated',
        entityType: 'lead',
        entityId: id,
        metadata: { from: existing.stage, to: input.stage },
      })
      // Best-effort webhook delivery
      try {
        await c.env.QUEUE.send({
          type: 'webhook.deliver',
          tenantId: session.tid,
          event: 'lead.stage_updated',
          payload: { leadId: id, from: existing.stage, to: input.stage },
        })
      } catch {
        // non-fatal
      }
    }

    return c.json({ lead })
  },
)

// PATCH /api/marketing/leads/:id/move
leadsRoute.patch(
  '/:id/move',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')
    const { id } = c.req.param()

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

    const existing = await getLead(db, session.tid, id)
    if (!existing) return c.json({ error: 'Not found' }, 404)

    const lead = await moveLead(db, session.tid, id, input)

    if (input.stage !== existing.stage) {
      await addLeadActivity(db, session.tid, id, session.sub, {
        type: 'stage_changed',
        metadata: { from: existing.stage, to: input.stage },
      })
    }

    return c.json({ lead })
  },
)

// DELETE /api/marketing/leads/:id
leadsRoute.delete('/:id', requirePermission('marketing:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

  const existing = await getLead(db, session.tid, id)
  if (!existing) return c.json({ error: 'Not found' }, 404)

  await archiveLead(db, session.tid, id)
  return c.json({ ok: true })
})

// POST /api/marketing/leads/:id/convert
leadsRoute.post(
  '/:id/convert',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')
    const { id } = c.req.param()

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

    const lead = await getLead(db, session.tid, id)
    if (!lead) return c.json({ error: 'Not found' }, 404)
    if (lead.stage !== 'WON') {
      return c.json({ error: 'Only WON leads can be converted to customers' }, 422)
    }
    if (lead.customerId) {
      return c.json({ error: 'Lead already converted' }, 422)
    }

    const result = await convertLeadToCustomer(db, session.tid, session.sub, id, {
      name: input.customer_data.name,
      email: input.customer_data.email ?? null,
      phone: input.customer_data.phone ?? null,
      company: input.customer_data.company ?? null,
    })

    return c.json({ ok: true, customerId: result.customerId, leadId: result.leadId }, 200)
  },
)

// ── Linked entities (wave-13: leads-detail-view) ─────────────────────────────

// GET /api/marketing/leads/:id/linked/proposals
leadsRoute.get('/:id/linked/proposals', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

  const lead = await getLead(db, session.tid, id)
  if (!lead) return c.json({ error: 'Not found' }, 404)

  const proposals = await listLinkedProposals(db, session.tid, id)
  return c.json({ proposals })
})

// GET /api/marketing/leads/:id/proposals
leadsRoute.get('/:id/proposals', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

  const lead = await getLead(db, session.tid, id)
  if (!lead) return c.json({ error: 'Not found' }, 404)

  const proposals = await listLinkedProposals(db, session.tid, id)
  return c.json({ proposals })
})

// GET /api/marketing/leads/:id/linked/contracts
leadsRoute.get('/:id/linked/contracts', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

  const lead = await getLead(db, session.tid, id)
  if (!lead) return c.json({ error: 'Not found' }, 404)

  const contracts = await listLinkedContracts(db, session.tid, id)
  return c.json({ contracts })
})

// GET /api/marketing/leads/:id/linked/invoices
leadsRoute.get('/:id/linked/invoices', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

  const lead = await getLead(db, session.tid, id)
  if (!lead) return c.json({ error: 'Not found' }, 404)

  const invoices = await listLinkedInvoices(db, session.tid, id)
  return c.json({ invoices })
})

// GET /api/marketing/leads/:id/linked/tasks
leadsRoute.get('/:id/linked/tasks', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

  const lead = await getLead(db, session.tid, id)
  if (!lead) return c.json({ error: 'Not found' }, 404)

  const linkedTasks = await listLinkedTasks(db, session.tid, id)
  return c.json({ tasks: linkedTasks })
})

// ── Activities ────────────────────────────────────────────────────────────────

// GET /api/marketing/leads/:id/activities
leadsRoute.get(
  '/:id/activities',
  requirePermission('marketing:read'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')
    const { id } = c.req.param()

    const limit = Math.min(Number(c.req.query('limit') ?? 50), 100)
    const cursor = c.req.query('cursor')

    const lead = await getLead(db, session.tid, id)
    if (!lead) return c.json({ error: 'Not found' }, 404)

    const result = await listLeadActivities(db, session.tid, id, limit, cursor)
    return c.json(result)
  },
)

// POST /api/marketing/leads/:id/activities
leadsRoute.post(
  '/:id/activities',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')
    const { id } = c.req.param()

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

    const lead = await getLead(db, session.tid, id)
    if (!lead) return c.json({ error: 'Not found' }, 404)

    // Only allow manual activity types via this endpoint
    const manualTypes = ['note', 'email_sent', 'call_logged'] as const
    if (!manualTypes.includes(input.type as (typeof manualTypes)[number])) {
      return c.json({ error: 'Invalid activity type for manual entry' }, 422)
    }

    const activity = await addLeadActivity(
      db,
      session.tid,
      id,
      session.sub,
      input,
    )
    return c.json({ activity }, 201)
  },
)

// ── Lead-to-Proposal flow (wave-10 leaf 6) ───────────────────────────────────

// POST /api/marketing/leads/:id/create-proposal
leadsRoute.post(
  '/:id/create-proposal',
  requirePermission('marketing:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
    const db = c.get('db')
    const { id } = c.req.param()

    try {
      const proposal = await createProposalFromLead(db, session.tid, session.sub, id)
      return c.json({ proposal }, 201)
    } catch (err) {
      if (err instanceof Error && err.message === 'Lead not found') {
        return c.json({ error: 'Lead not found' }, 404)
      }
      throw err
    }
  },
)

// ── Lead lost re-engagement (wave-14) ────────────────────────────────────────

// GET /api/marketing/leads/lost-reasons — must be BEFORE /:id to avoid capture
leadsRoute.get('/lost-reasons', requirePermission('marketing:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const reasons = await getLostReasons(db, session.tid)
  return c.json({ reasons })
})

// PATCH /api/marketing/leads/:id/reopen
leadsRoute.patch('/:id/reopen', requirePermission('marketing:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const db = c.get('db')
  const { id } = c.req.param()

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

  const existing = await getLead(db, session.tid, id)
  if (!existing) return c.json({ error: 'Not found' }, 404)

  const lead = await reopenLead(db, session.tid, id, parsed.data.stage ?? 'NEW')

  await addLeadActivity(db, session.tid, id, session.sub, {
    type: 'stage_changed',
    metadata: { from: existing.stage, to: lead.stage, reason: 'reopened' },
  })
  await logAuditEvent(c, {
    tenantId: session.tid,
    userId: session.sub,
    eventType: 'lead.reopened',
    entityType: 'lead',
    entityId: id,
    metadata: { from: existing.stage, to: lead.stage },
  })

  return c.json({ lead })
})

// ── Lead scoring (wave-14: lead-qualification-scoring) ────────────────────────
// POST /:id/score + GET /:id/score — must be BEFORE broad /:id routes
leadsRoute.route('', leadScoreRoute)
