/**
 * Support Center ticket routes — crm-support-center.
 * Mounted at /api/tickets (behind authMiddleware in routes/support/router.ts).
 *
 * GET    /                    list tickets (filterable, cursor-paginated)
 * POST   /                    create ticket (staff)
 * GET    /categories           list categories
 * POST   /categories           create category
 * DELETE /categories/:id       delete category
 * GET    /:id                  ticket detail + messages
 * PATCH  /:id                  update (status, priority, assignee, category)
 * DELETE /:id                  soft delete
 * POST   /:id/reply            staff reply
 * GET    /:id/messages         message stream
 * DELETE /:id/messages/:mid    soft delete own message
 */
import { Hono } from 'hono'
import type { AppEnv } from '../../types'
import { requirePermission } from '../../middleware/guards'
import {
  listTickets,
  getTicket,
  createTicket,
  updateTicket,
  softDeleteTicket,
  createTicketMessage,
  appendStatusTransitionMessage,
  listTicketMessages,
  softDeleteTicketMessage,
  getTicketMessage,
  listTicketCategories,
  createTicketCategory,
  deleteTicketCategory,
  createTicketSchema,
  updateTicketSchema,
  replyTicketSchema,
  createTicketCategorySchema,
  ticketFiltersSchema,
  assertTenantOwnsCustomer,
  assertTenantOwnsContact,
  assertTenantOwnsTicketCategory,
  assertActiveTenantAssignee,
  invalidTenantReferenceBody,
  findUserById,
  createNotification,
  getSlaEnabled,
  getSlaPolicyByPriority,
  getOwnerAdminUserIds,
  computeAndSetDueAt,
  markFirstResponse,
} from '@zync/db/queries'
import { publishRealtimeEvent } from '@zync/realtime/server'
import { sanitizeCommentHtml } from '../../lib/sanitize-comment'
import { enqueueTicketWebhook } from '../../lib/ticket-webhooks'
import { routeTicketReplyToChannel } from '../../services/route-ticket-reply'

export const supportRoute = new Hono<AppEnv>()

// ── Categories ────────────────────────────────────────────────────────────────

// GET /api/tickets/categories
supportRoute.get('/categories', requirePermission('tickets: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 categories = await listTicketCategories(db, session.tid)
  return c.json({ categories }, 200)
})

// POST /api/tickets/categories
supportRoute.post('/categories', requirePermission('tickets:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const parsed = createTicketCategorySchema.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 422)
  }
  const db = c.get('db')
  const category = await createTicketCategory(db, session.tid, parsed.data)
  return c.json(category, 201)
})

// DELETE /api/tickets/categories/:id
supportRoute.delete('/categories/:id', requirePermission('tickets: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')
  await deleteTicketCategory(db, session.tid, c.req.param('id'))
  return c.json({ ok: true }, 200)
})

// ── Ticket CRUD ───────────────────────────────────────────────────────────────

// GET /api/tickets
supportRoute.get('/', requirePermission('tickets: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 rawQuery = Object.fromEntries(url.searchParams.entries())
  const parsed = ticketFiltersSchema.safeParse(rawQuery)
  if (!parsed.success) {
    return c.json({ error: 'Invalid query', issues: parsed.error.issues }, 422)
  }

  const q = parsed.data
  const db = c.get('db')
  const result = await listTickets(
    db,
    session.tid,
    {
      status: q.status,
      priority: q.priority,
      assignee_id: q.assignee_id,
      category_id: q.category_id,
      customer_id: q.customer_id,
      source: q.source,
      sla_breached: q.sla_breached,
      q: q.q,
    },
    q.cursor,
    q.limit,
  )
  return c.json(result, 200)
})

// POST /api/tickets
supportRoute.post('/', requirePermission('tickets:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const parsed = createTicketSchema.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 422)
  }
  const db = c.get('db')

  if (!(await assertTenantOwnsCustomer(db, session.tid, parsed.data.customer_id))) {
    return c.json(invalidTenantReferenceBody('customer_id'), 400)
  }
  if (
    !(await assertTenantOwnsContact(
      db,
      session.tid,
      parsed.data.contact_id,
      parsed.data.customer_id,
    ))
  ) {
    return c.json(invalidTenantReferenceBody('contact_id'), 400)
  }
  if (!(await assertTenantOwnsTicketCategory(db, session.tid, parsed.data.category_id))) {
    return c.json(invalidTenantReferenceBody('category_id'), 400)
  }
  if (!(await assertActiveTenantAssignee(db, session.tid, parsed.data.assignee_id))) {
    return c.json(invalidTenantReferenceBody('assignee_id'), 400)
  }

  const ticket = await createTicket(db, session.tid, parsed.data)
  if (await getSlaEnabled(db, session.tid)) {
    await computeAndSetDueAt(
      db,
      session.tid,
      ticket.id,
      ticket.priority,
      new Date(ticket.created_at),
    )
  }
  return c.json(ticket, 201)
})

// GET /api/tickets/:id
supportRoute.get('/:id', requirePermission('tickets: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 ticket = await getTicket(db, session.tid, c.req.param('id'))
  if (!ticket) return c.json({ error: 'Not found' }, 404)
  const messages = await listTicketMessages(db, session.tid, ticket.id)
  return c.json({ ticket, messages }, 200)
})

// PATCH /api/tickets/:id
supportRoute.patch('/:id', requirePermission('tickets:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const parsed = updateTicketSchema.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 422)
  }
  const db = c.get('db')
  const ticketId = c.req.param('id')

  if (parsed.data.category_id !== undefined) {
    if (!(await assertTenantOwnsTicketCategory(db, session.tid, parsed.data.category_id))) {
      return c.json(invalidTenantReferenceBody('category_id'), 400)
    }
  }
  if (parsed.data.assignee_id !== undefined) {
    if (!(await assertActiveTenantAssignee(db, session.tid, parsed.data.assignee_id))) {
      return c.json(invalidTenantReferenceBody('assignee_id'), 400)
    }
  }

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

  const ticket = await updateTicket(db, session.tid, ticketId, parsed.data)

  if (
    parsed.data.priority !== undefined &&
    parsed.data.priority !== existing.priority &&
    (await getSlaEnabled(db, session.tid))
  ) {
    await computeAndSetDueAt(
      db,
      session.tid,
      ticket.id,
      parsed.data.priority,
      new Date(existing.created_at),
    )
  }

  if (parsed.data.status !== undefined && parsed.data.status !== existing.status) {
    const actor = await findUserById(db, session.sub)
    const actorLabel = actor?.name ?? 'Staff'
    await appendStatusTransitionMessage(db, session.tid, ticket.id, parsed.data.status, actorLabel)

    if (parsed.data.status === 'resolved') {
      await enqueueTicketWebhook(c.env, session.tid, 'ticket.resolved', {
        ticketId: ticket.id,
        resolvedAt: ticket.resolved_at,
        resolvedBy: session.sub,
      })
    }

    try {
      await publishRealtimeEvent(c.env.REALTIME_QUEUE, {
        type: 'ticket.status_changed',
        tenantId: session.tid,
        payload: { ticketId: ticket.id },
      })
    } catch {
      // Non-fatal
    }
  }

  return c.json(ticket, 200)
})

// DELETE /api/tickets/:id
supportRoute.delete('/:id', requirePermission('tickets:delete'), 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')
  await softDeleteTicket(db, session.tid, c.req.param('id'))
  return c.json({ ok: true }, 200)
})

// ── Messages ──────────────────────────────────────────────────────────────────

// GET /api/tickets/:id/messages
supportRoute.get('/:id/messages', requirePermission('tickets: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')
  // Verify ticket belongs to tenant
  const ticket = await getTicket(db, session.tid, c.req.param('id'))
  if (!ticket) return c.json({ error: 'Not found' }, 404)
  const messages = await listTicketMessages(db, session.tid, ticket.id)
  return c.json({ messages }, 200)
})

// POST /api/tickets/:id/reply
supportRoute.post('/:id/reply', requirePermission('tickets:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const parsed = replyTicketSchema.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 422)
  }

  const db = c.get('db')
  const ticket = await getTicket(db, session.tid, c.req.param('id'))
  if (!ticket) return c.json({ error: 'Not found' }, 404)
  if (ticket.status === 'closed') {
    return c.json({ error: 'Ticket is closed — no further replies accepted' }, 409)
  }

  const sanitizedContent = sanitizeCommentHtml(parsed.data.content)

  // Save the staff reply message
  const message = await createTicketMessage(db, session.tid, ticket.id, {
    author_type: 'staff',
    author_id: session.sub,
    content: sanitizedContent,
    source: 'web',
  })

  const firstResponseWasUnset = ticket.first_response_at == null
  await markFirstResponse(db, session.tid, ticket.id)

  if (firstResponseWasUnset && (await getSlaEnabled(db, session.tid))) {
    const policy = await getSlaPolicyByPriority(db, session.tid, ticket.priority)
    if (policy && policy.first_response_hours > 0) {
      const deadline =
        new Date(ticket.created_at).getTime() + policy.first_response_hours * 60 * 60 * 1000
      if (Date.now() > deadline) {
        const recipients = new Set<string>()
        if (ticket.assignee_id) recipients.add(ticket.assignee_id)
        for (const userId of await getOwnerAdminUserIds(db, session.tid)) {
          recipients.add(userId)
        }
        for (const userId of recipients) {
          await createNotification(db, {
            tenantId: session.tid,
            userId,
            type: 'ticket_escalated',
            titleKey: 'notification.ticket_escalated.title',
            bodyKey: 'notification.ticket_escalated.body',
            params: { ticketNumber: ticket.id },
            entityType: 'ticket',
            entityId: ticket.id,
          })
        }
      }
    }
  }

  // Update ticket status to pending_customer after staff reply
  if (ticket.status === 'open' || ticket.status === 'in_progress') {
    await updateTicket(db, session.tid, ticket.id, { status: 'pending_customer' })
    const actor = await findUserById(db, session.sub)
    await appendStatusTransitionMessage(
      db,
      session.tid,
      ticket.id,
      'pending_customer',
      actor?.name ?? 'Staff',
    )
  }

  await routeTicketReplyToChannel(c.env, db, session.tid, ticket, sanitizedContent).catch((err) => {
    console.error('[support/reply] Channel dispatch failed:', err)
  })

  await enqueueTicketWebhook(c.env, session.tid, 'ticket.replied', {
    ticketId: ticket.id,
    messageId: message.id,
    authorType: 'staff',
    source: ticket.source,
  })

  try {
    await publishRealtimeEvent(c.env.REALTIME_QUEUE, {
      type: 'ticket.message_added',
      tenantId: session.tid,
      payload: {
        ticketId: ticket.id,
        messageId: message.id,
        authorId: session.sub,
        authorName: '',
        preview: sanitizedContent.slice(0, 200),
      },
    })
  } catch {
    // Non-fatal
  }

  return c.json(message, 201)
})

// DELETE /api/tickets/:id/messages/:mid
supportRoute.delete('/:id/messages/:mid', requirePermission('tickets: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 ticketId = c.req.param('id')
  const messageId = c.req.param('mid')

  const ticket = await getTicket(db, session.tid, ticketId)
  if (!ticket) return c.json({ error: 'Not found' }, 404)

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

  if (existing.author_type === 'system') {
    return c.json({ error: 'System messages cannot be deleted' }, 403)
  }
  if (existing.author_type !== 'staff' || existing.author_id !== session.sub) {
    return c.json({ error: 'You can only delete your own messages' }, 403)
  }

  await softDeleteTicketMessage(db, session.tid, ticketId, messageId)
  return c.json({ ok: true }, 200)
})
