/**
 * Rotas de Chats — API REST para a inbox do frontend.
 *
 * Endpoints:
 *   GET    /api/chats                          — Lista chats (com filtros e último msg)
 *   GET    /api/chats/search/messages           — Busca full-text em mensagens
 *   GET    /api/chats/stats/overview            — Contagens para o dashboard
 *   GET    /api/chats/:id                       — Detalhe de um chat
 *   POST   /api/chats/:id/read                  — Zera unread_count
 *   PATCH  /api/chats/:id/archive               — Arquiva/desarquiva
 *   PATCH  /api/chats/:id/pin                   — Fixa/desfixa
 *   DELETE /api/chats/:id                       — Remove chat e mensagens
 *   GET    /api/chats/:id/messages              — Histórico paginado por cursor
 *   GET    /api/chats/:id/protocol              — Protocolos do chat
 *   POST   /api/chats/:id/protocol              — Abre novo protocolo
 *   PATCH  /api/chats/:chatId/protocol/:protocolId — Atualiza protocolo
 *
 * RESOLUÇÃO DE NOME (displayName):
 *   - Grupos: usa Chat.name (subject do grupo, sincronizado por groups.upsert)
 *   - Individuais: Contact.name > Contact.verifiedName > Contact.notify > lastMsg.pushName
 *   - Se nenhum nome disponível: retorna null → frontend formata o telefone
 *
 * @see ChatRepository — queries otimizadas com JOINs
 * @see MessageRepository — busca e paginação de mensagens
 */
import { Router, type Request, type Response } from 'express'
import { z } from 'zod'
import { ChatRepository } from './chat.repository'
import { MessageRepository } from './message.repository'
import { prisma } from '../../infra/database/prisma'
import { dragonfly } from '../../infra/cache/dragonfly'
import type { WhatsAppConnectionManager } from '../whatsapp/connection/WhatsAppConnectionManager'

const chatRepo = new ChatRepository()
const msgRepo = new MessageRepository()

/** Handler genérico de erros: diferencia ZodError (400) de erros internos (500) */
function handleError(res: Response, err: unknown) {
  if (err instanceof z.ZodError) {
    res.status(400).json({ error: 'Dados inválidos', details: err.flatten().fieldErrors })
    return
  }
  res.status(500).json({ error: (err as Error).message ?? 'Erro interno' })
}

/**
 * Constrói as rotas de chat.
 *
 * Montadas em: /api/chats
 * O WhatsAppConnectionManager é injetado mas não usado diretamente aqui
 * (reservado para futuras features como typing indicator).
 */
export function buildChatRoutes(manager: WhatsAppConnectionManager): Router {
  const router = Router()

  // ═══════════════════════════════════════════════════════════════════════════
  //  GET /api/chats — Lista chats com snapshot da última mensagem
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Lista chats do tenant para uma instância, com:
   *   - Último mensagem (preview para a lista de chats)
   *   - Protocolo ativo (se houver)
   *   - Dados do contato (nome resolvido, avatar, reputação)
   *
   * Query params:
   *   - instanceId: UUID (obrigatório)
   *   - search?: string (busca por nome/telefone)
   *   - archived?: boolean (filtrar por arquivados)
   *   - limit?: number (1-200)
   *
   * REGRA DE RESOLUÇÃO DE NOME (resolvedName):
   *   Grupos: Chat.name (subject do grupo)
   *   Individuais:
   *     1. Contact.name (agenda do telefone — via contacts.upsert)
   *     2. Contact.verifiedName (conta business verificada)
   *     3. Contact.notify (pushname — via contacts.upsert ou mensagem)
   *     4. lastMsg.pushName (fallback: pushName da última mensagem recebida)
   *     5. null (frontend formata o telefone como "55 11 99999-9999")
   */
  router.get('/', async (req: Request, res: Response) => {
    try {
      const { instanceId, search, archived, limit } = z.object({
        instanceId: z.string().uuid(),
        search: z.string().optional(),
        archived: z.coerce.boolean().optional(),
        limit: z.coerce.number().int().min(1).max(200).optional(),
      }).parse(req.query)

      const chats = await chatRepo.findAllByInstance(req.tenantId!, instanceId, {
        search,
        archived,
        limit,
      })

      // Serializa cada chat com nome resolvido e dados formatados para o frontend
      const result = chats.map((c) => {
        const contact = c.contact
        const isGroup = c.jid.endsWith('@g.us')

        // Última mensagem (já vem do JOIN no repository, limitado a 1)
        const lastMsg = c.messages[0]
        // Protocolo ativo mais recente (OPEN ou IN_PROGRESS)
        const activeProtocol = c.protocols[0] ?? null

        // ── Resolução de nome ─────────────────────────────────────────────
        // Regra: nunca retorna o JID bruto como nome — retorna null e deixa
        // o frontend fazer o fallback (telefone formatado ou "Grupo").
        //
        // Para individuais: usa hierarchia Contact > lastMsg.pushName > null
        // Para grupos: usa Chat.name (subject) ou null
        //
        // O fallback lastMsg.pushName cobre contatos que vieram do history
        // sync sem nome (a maioria — só ~22 de ~3000 têm verifiedName).
        const resolvedName = isGroup
          ? (c.name ?? null)
          : (contact?.name ?? contact?.verifiedName ?? contact?.notify
             ?? (lastMsg && !lastMsg.fromMe ? lastMsg.pushName : null)
             ?? null)

        return {
          id: c.id,
          instanceId: c.instanceId,
          jid: c.jid,
          name: c.name ?? null,
          isPinned: c.isPinned,
          isArchived: c.isArchived,
          unreadCount: c.unreadCount,
          lastMessageAt: c.lastMessageAt,
          createdAt: c.createdAt,
          contact: contact
            ? {
                name: resolvedName,   // null quando não há nome real
                phone: contact.phone,
                avatarUrl: contact.avatarUrl,
                scoreReputacao: contact.scoreReputacao,
                flagRestricao: contact.flagRestricao,
              }
            : isGroup
            ? { name: resolvedName, phone: null, avatarUrl: null, scoreReputacao: 0, flagRestricao: false }
            : null,
          lastMessage: lastMsg
            ? {
                body: lastMsg.body,
                fromMe: lastMsg.fromMe,
                status: lastMsg.status,
                type: lastMsg.type,
                timestamp: lastMsg.timestamp,
                pushName: lastMsg.fromMe ? null : (lastMsg.pushName ?? null),
              }
            : null,
          protocol: activeProtocol
            ? { number: activeProtocol.number, status: activeProtocol.status }
            : null,
        }
      })

      res.json(result)
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  GET /api/chats/search/messages — Busca full-text em mensagens
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Busca full-text no corpo das mensagens.
   * MUST ser definido ANTES de /:id para evitar captura de "search" como param.
   *
   * Query params:
   *   - instanceId: UUID
   *   - q: string (mínimo 2 chars)
   *   - limit?: number (1-50)
   */
  router.get('/search/messages', async (req: Request, res: Response) => {
    try {
      const { instanceId, q, limit } = z.object({
        instanceId: z.string().uuid(),
        q: z.string().min(2),
        limit: z.coerce.number().int().min(1).max(50).optional(),
      }).parse(req.query)

      const results = await msgRepo.search(req.tenantId!, instanceId, q, limit)
      res.json(results)
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  GET /api/chats/stats/overview — Contagens para o dashboard
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Retorna métricas resumidas para o dashboard do frontend.
   * MUST ser definido ANTES de /:id para evitar captura de "stats" como param.
   *
   * Response: { totalUnread, openProtocols, totalChats }
   *
   * Cache DragonflyDB de 30s para não sobrecarregar o banco em dashboards
   * com refresh automático.
   */
  router.get('/stats/overview', async (req: Request, res: Response) => {
    try {
      const { instanceId } = z.object({ instanceId: z.string().uuid() }).parse(req.query)

      const cacheKey = `stats:${req.tenantId}:${instanceId}`
      const cached = await dragonfly.getJson(cacheKey)
      if (cached) { res.json(cached); return }

      // Executa as 3 queries em paralelo para minimizar latência
      const [totalUnread, openProtocols, totalChats] = await Promise.all([
        chatRepo.countUnreadByInstance(req.tenantId!, instanceId),
        prisma.protocol.count({
          where: {
            tenantId: req.tenantId!,
            status: { in: ['OPEN', 'IN_PROGRESS'] },
          },
        }),
        prisma.chat.count({
          where: {
            tenantId: req.tenantId!,
            instanceId,
            isArchived: false,
            // Exclui chats @lid (artefatos internos do WhatsApp)
            NOT: { jid: { endsWith: '@lid' } }
          },
        }),
      ])

      const stats = { totalUnread, openProtocols, totalChats }
      await dragonfly.setJson(cacheKey, stats, 30) // cache 30s

      res.json(stats)
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  GET /api/chats/:id — Detalhe de um chat
  // ═══════════════════════════════════════════════════════════════════════════

  router.get('/:id', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      const chat = await chatRepo.findById(id, req.tenantId!)
      if (!chat) { res.status(404).json({ error: 'Chat não encontrado' }); return }
      res.json(chat)
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  POST /api/chats/:id/read — Zera contador de não-lidas
  // ═══════════════════════════════════════════════════════════════════════════

  /** Marca todas as mensagens do chat como lidas (zera unreadCount) */
  router.post('/:id/read', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      await chatRepo.clearUnread(id, req.tenantId!)
      res.json({ ok: true })
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  PATCH /api/chats/:id/archive — Arquivar/desarquivar
  // ═══════════════════════════════════════════════════════════════════════════

  /** Body: { archived: boolean } */
  router.patch('/:id/archive', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      const { archived } = z.object({ archived: z.boolean() }).parse(req.body)
      await chatRepo.setArchived(id, req.tenantId!, archived)
      res.json({ ok: true })
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  PATCH /api/chats/:id/pin — Fixar/desfixar
  // ═══════════════════════════════════════════════════════════════════════════

  /** Body: { pinned: boolean } */
  router.patch('/:id/pin', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      const { pinned } = z.object({ pinned: z.boolean() }).parse(req.body)
      await chatRepo.setPinned(id, req.tenantId!, pinned)
      res.json({ ok: true })
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  DELETE /api/chats/:id — Remove chat e mensagens
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Remove um chat e TODAS as suas mensagens permanentemente.
   * Operação irreversível — use com cuidado.
   * Remove mensagens primeiro (FK constraint) e depois o chat.
   */
  router.delete('/:id', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      const chat = await chatRepo.findById(id, req.tenantId!)
      if (!chat) { res.status(404).json({ error: 'Chat não encontrado' }); return }
      await prisma.message.deleteMany({ where: { chatId: id } })
      await prisma.chat.delete({ where: { id } })
      res.status(204).send()
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  GET /api/chats/:id/messages — Histórico paginado
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Retorna mensagens do chat com paginação por cursor (timestamp ISO).
   *
   * Query params:
   *   - before?: ISO datetime (mensagens anteriores a este timestamp)
   *   - after?: ISO datetime (mensagens posteriores a este timestamp)
   *   - limit?: number (1-100, default definido no repository)
   *
   * Response: { messages: Message[], hasMore: boolean }
   *
   * O frontend usa infinite scroll: carrega as mais recentes primeiro,
   * depois pede mais antigas passando `before` do timestamp mais antigo.
   */
  router.get('/:id/messages', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      const { before, after, limit } = z.object({
        before: z.string().datetime().optional(),
        after: z.string().datetime().optional(),
        limit: z.coerce.number().int().min(1).max(100).optional(),
      }).parse(req.query)

      // Valida que o chat pertence ao tenant (segurança multi-tenant)
      const chat = await chatRepo.findById(id, req.tenantId!)
      if (!chat) { res.status(404).json({ error: 'Chat não encontrado' }); return }

      const { messages, hasMore } = await msgRepo.findByChatPaginated(id, {
        before,
        after,
        limit,
      })

      res.json({ messages, hasMore })
    } catch (err) {
      handleError(res, err)
    }
  })

  // ═══════════════════════════════════════════════════════════════════════════
  //  PROTOCOLOS DE ATENDIMENTO
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * GET /api/chats/:id/protocol — Últimos 10 protocolos do chat.
   * Inclui setor e equipe associados.
   */
  router.get('/:id/protocol', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      const chat = await chatRepo.findById(id, req.tenantId!)
      if (!chat) { res.status(404).json({ error: 'Chat não encontrado' }); return }

      const protocols = await prisma.protocol.findMany({
        where: { chatId: id },
        orderBy: { createdAt: 'desc' },
        take: 10,
        include: {
          sector: { select: { name: true } },
          team: { select: { name: true } },
        },
      })

      res.json(protocols)
    } catch (err) {
      handleError(res, err)
    }
  })

  /**
   * POST /api/chats/:id/protocol — Abre novo protocolo de atendimento.
   *
   * Body (opcional):
   *   - sectorId?: UUID — setor responsável
   *   - teamId?: UUID — equipe responsável
   *
   * O número do protocolo é gerado sequencialmente: #0001-2026, #0002-2026, etc.
   * Exige que o chat tenha um contactId vinculado (não funciona para chats órfãos).
   */
  router.post('/:id/protocol', async (req: Request, res: Response) => {
    try {
      const id = req.params['id'] as string
      const { sectorId, teamId } = z.object({
        sectorId: z.string().uuid().optional(),
        teamId: z.string().uuid().optional(),
      }).parse(req.body)

      const chat = await chatRepo.findById(id, req.tenantId!)
      if (!chat) { res.status(404).json({ error: 'Chat não encontrado' }); return }
      if (!chat.contactId) { res.status(400).json({ error: 'Chat sem contato associado' }); return }

      // Gera número de protocolo sequencial com ano
      const count = await prisma.protocol.count({ where: { chatId: id } })
      const year = new Date().getFullYear()
      const number = `#${String(count + 1).padStart(4, '0')}-${year}`

      const protocol = await prisma.protocol.create({
        data: {
          tenantId: req.tenantId!,
          number,
          chatId: id,
          contactId: chat.contactId,
          sectorId,
          teamId,
          status: 'OPEN',
        },
      })

      res.status(201).json(protocol)
    } catch (err) {
      handleError(res, err)
    }
  })

  /**
   * PATCH /api/chats/:chatId/protocol/:protocolId — Atualiza protocolo.
   *
   * Body:
   *   - status?: enum — novo status do protocolo
   *   - summary?: string (max 500) — resumo do atendimento
   *
   * Se status='RESOLVED', preenche resolvedAt automaticamente.
   *
   * Status possíveis:
   *   OPEN → IN_PROGRESS → WAITING_CLIENT / WAITING_AGENT → RESOLVED / CANCELLED
   */
  router.patch('/:chatId/protocol/:protocolId', async (req: Request, res: Response) => {
    try {
      const chatId = req.params['chatId'] as string
      const protocolId = req.params['protocolId'] as string
      const { status, summary } = z.object({
        status: z.enum(['OPEN', 'IN_PROGRESS', 'WAITING_CLIENT', 'WAITING_AGENT', 'RESOLVED', 'CANCELLED']).optional(),
        summary: z.string().max(500).optional(),
      }).parse(req.body)

      const protocol = await prisma.protocol.updateMany({
        where: { id: protocolId, chatId, tenantId: req.tenantId! },
        data: {
          ...(status && { status }),
          ...(summary && { summary }),
          ...(status === 'RESOLVED' && { resolvedAt: new Date() }),
        },
      })

      res.json({ updated: protocol.count })
    } catch (err) {
      handleError(res, err)
    }
  })

  return router
}
