/**
 * Task messages + audit routes — tasks-detail-communication.
 *
 * Mounted at /api/tasks (merged with board engine routes).
 *
 * GET    /api/tasks/:id/messages         → PaginatedResponse<TaskMessage>
 * POST   /api/tasks/:id/messages         → TaskMessage
 * DELETE /api/tasks/:id/messages/:mid    → 204
 * GET    /api/tasks/:id/audit            → PaginatedResponse<TaskAuditRow>
 *
 * Permissions:
 *   tasks:read  — GET routes
 *   tasks:write — POST / DELETE
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission } from '../middleware/guards'
import { requireModuleEnabled } from '../middleware/require-module-enabled'
import {
  listTaskMessages,
  createTaskMessage,
  softDeleteTaskMessage,
  reparentAttachments,
  listTaskAudit,
  createNotification,
  getTask,
} from '@zync/db/queries'
import { sanitizeCommentHtml } from '../lib/sanitize-comment'
import { publishRealtimeEvent } from '@zync/realtime/server'
import type { TaskMessage, TaskMessageAttachment } from '@zync/types'

// ── Zod schemas ───────────────────────────────────────────────────────────────

const createMessageSchema = z.object({
  contentHtml: z.string().min(1).max(100_000),
  attachmentIds: z.array(z.string().uuid()).max(10).optional(),
})

const listMessagesQuerySchema = z.object({
  cursor: z.string().optional(),
  limit: z.coerce.number().int().min(1).max(50).default(50),
})

const listAuditQuerySchema = z.object({
  cursor: z.string().optional(),
  limit: z.coerce.number().int().min(1).max(50).default(50),
})

// ── Serializer — strip r2Key from wire shape ──────────────────────────────────

function serializeAttachmentWire(att: {
  id: string; filename: string; url: string; r2Key: string;
  sizeBytes: number; mimeType: string; createdAt: string;
}): TaskMessageAttachment {
  return {
    id: att.id,
    filename: att.filename,
    url: att.url,
    sizeBytes: att.sizeBytes,
    mimeType: att.mimeType,
    createdAt: att.createdAt,
  }
}

function serializeMessageWire(row: {
  id: string; taskId: string; authorId: string | null; authorName: string | null;
  authorAvatarUrl: string | null; messageType: 'comment' | 'system'; content: string;
  deleted: boolean; attachments: Array<{ id: string; filename: string; url: string; r2Key: string; sizeBytes: number; mimeType: string; createdAt: string }>; createdAt: string;
}): TaskMessage {
  return {
    id: row.id,
    taskId: row.taskId,
    authorId: row.authorId,
    authorName: row.authorName,
    authorAvatarUrl: row.authorAvatarUrl,
    messageType: row.messageType,
    content: row.content,
    deleted: row.deleted,
    attachments: row.attachments.map(serializeAttachmentWire),
    createdAt: row.createdAt,
  }
}

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

export const taskMessagesRouter = new Hono<AppEnv>()

// All task-message routes require auth + tasks module enabled
taskMessagesRouter.use('*', authMiddleware)
taskMessagesRouter.use('*', requireModuleEnabled('tasks'))

// ── GET /api/tasks/:id/messages ───────────────────────────────────────────────

taskMessagesRouter.get(
  '/:id/messages',
  requirePermission('tasks:read'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const parsed = listMessagesQuerySchema.safeParse({
      cursor: c.req.query('cursor'),
      limit: c.req.query('limit'),
    })
    if (!parsed.success) {
      return c.json({ error: 'Bad Request', issues: parsed.error.issues }, 400)
    }

    const db = c.get('db')
    const taskId = c.req.param('id')
    const task = await getTask(db, session.tid, taskId)
    if (!task) {
      return c.json({ error: 'Not found' }, 404)
    }
    const result = await listTaskMessages(db, session.tid, taskId, {
      limit: parsed.data.limit,
      cursor: parsed.data.cursor,
    })

    return c.json({
      rows: result.rows.map(serializeMessageWire),
      nextCursor: result.nextCursor,
    })
  },
)

// ── POST /api/tasks/:id/messages ──────────────────────────────────────────────

taskMessagesRouter.post(
  '/:id/messages',
  requirePermission('tasks: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 = createMessageSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 422)
    }

    const db = c.get('db')
    const taskId = c.req.param('id')
    const tenantId = session.tid
    const authorId = session.sub

    const task = await getTask(db, tenantId, taskId)
    if (!task) {
      return c.json({ error: 'Not found' }, 404)
    }

    // Server-side sanitize the HTML before storage
    const sanitizedHtml = sanitizeCommentHtml(parsed.data.contentHtml)

    // Insert the message
    const msgRow = await createTaskMessage(db, {
      tenantId,
      taskId,
      authorId,
      messageType: 'comment',
      content: sanitizedHtml,
    })

    // Reparent any pre-uploaded draft attachments to this message
    const attachmentIds = parsed.data.attachmentIds ?? []
    const reparented = await reparentAttachments(db, tenantId, attachmentIds, msgRow.id)
    const wireAttachments: TaskMessageAttachment[] = reparented.map(serializeAttachmentWire)

    const wireMsg: TaskMessage = {
      ...serializeMessageWire(msgRow),
      attachments: wireAttachments,
    }

    // Publish task.message_added for real-time fan-out (queue → DO → clients)
    try {
      await publishRealtimeEvent(c.env.REALTIME_QUEUE, {
        type: 'task.message_added',
        tenantId,
        payload: { taskId, message: wireMsg },
      })
    } catch {
      // Non-fatal — client will poll
    }

    // Emit task_comment notification to task participants (fire-and-forget)
    // We notify the tenant; in a full implementation we'd look up watchers.
    // Here we create a notification for the current user's tenant.
    createNotification(db, {
      tenantId,
      userId: authorId,
      type: 'task_comment',
      titleKey: 'notifications.task_comment.title',
      bodyKey: 'notifications.task_comment.body',
      params: { taskId, authorId },
      entityType: 'task',
      entityId: taskId,
    }).catch(() => {
      // Non-fatal
    })

    return c.json(wireMsg, 201)
  },
)

// ── DELETE /api/tasks/:id/messages/:mid ───────────────────────────────────────

taskMessagesRouter.delete(
  '/:id/messages/:mid',
  requirePermission('tasks: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 taskId = c.req.param('id')
    const messageId = c.req.param('mid')
    const tenantId = session.tid
    const authorId = session.sub

    const result = await softDeleteTaskMessage(db, {
      tenantId,
      messageId,
      authorId,
    })

    if (!result) {
      // Could be: not found, not author, or system message
      return c.json({ error: 'Not found or not authorized' }, 404)
    }

    const wireMsg = serializeMessageWire(result)

    // Publish the deleted-placeholder update for real-time fan-out
    try {
      await publishRealtimeEvent(c.env.REALTIME_QUEUE, {
        type: 'task.message_added',
        tenantId,
        payload: { taskId, message: wireMsg },
      })
    } catch {
      // Non-fatal
    }

    return c.body(null, 204)
  },
)

// ── GET /api/tasks/:id/audit ──────────────────────────────────────────────────

taskMessagesRouter.get(
  '/:id/audit',
  requirePermission('tasks:read'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const parsed = listAuditQuerySchema.safeParse({
      cursor: c.req.query('cursor'),
      limit: c.req.query('limit'),
    })
    if (!parsed.success) {
      return c.json({ error: 'Bad Request', issues: parsed.error.issues }, 400)
    }

    const db = c.get('db')
    const taskId = c.req.param('id')
    const task = await getTask(db, session.tid, taskId)
    if (!task) {
      return c.json({ error: 'Not found' }, 404)
    }
    const result = await listTaskAudit(db, session.tid, taskId, {
      limit: parsed.data.limit,
      cursor: parsed.data.cursor,
    })

    return c.json({
      rows: result.rows,
      nextCursor: result.nextCursor,
    })
  },
)
