import { Prisma } from '@prisma/client'
import { prisma } from '../../infra/database/prisma'

interface ContactSlim {
  id: string
  name: string | null
  notify: string | null
  verifiedName: string | null
  phone: string | null
  avatarUrl: string | null
  scoreReputacao: number
  flagRestricao: boolean
}

interface ProtocolSlim {
  chatId: string
  number: string
  status: string
}

interface LastMsgSlim {
  chatId: string
  body: string | null
  fromMe: boolean
  status: string
  timestamp: Date
  type: string
  pushName: string | null
}

export class ChatRepository {
  async findAllByInstance(tenantId: string, instanceId: string, opts: {
    limit?: number
    search?: string
    archived?: boolean
  } = {}) {
    const { limit = 200, search, archived = false } = opts

    // ── Q1: chats (sem includes — bate o N+1) ────────────────────────────────
    const chats = await prisma.chat.findMany({
      where: {
        tenantId,
        instanceId,
        isArchived: archived,
        NOT: { OR: [
          { jid: { endsWith: '@lid' } },
          { jid: { contains: '@broadcast' } },
          { jid: { endsWith: '@newsletter' } },
        ]},
        ...(search
          ? {
              OR: [
                { jid: { contains: search, mode: 'insensitive' as const } },
                { contact: { name: { contains: search, mode: 'insensitive' as const } } },
                { contact: { notify: { contains: search, mode: 'insensitive' as const } } },
                { contact: { phone: { contains: search, mode: 'insensitive' as const } } },
              ],
            }
          : {}),
      },
      orderBy: [
        { isPinned: 'desc' },
        { lastMessageAt: { sort: 'desc', nulls: 'last' } },
        { createdAt: 'desc' },
      ],
      take: limit,
    })

    if (chats.length === 0) {
      return [] as Array<typeof chats[number] & {
        contact: ContactSlim | null
        protocols: Array<{ number: string; status: string }>
        messages: Array<Omit<LastMsgSlim, 'chatId'>>
      }>
    }

    const chatIds = chats.map((c) => c.id)
    const contactIds = Array.from(new Set(chats.map((c) => c.contactId).filter((x): x is string => !!x)))

    // ── Q2/Q3/Q4 em paralelo: contatos, protocols (1 por chat), last msg (1 por chat) ──
    const [contacts, protocols, lastMsgs] = await Promise.all([
      contactIds.length > 0
        ? prisma.contact.findMany({
            where: { id: { in: contactIds } },
            select: {
              id: true,
              name: true,
              notify: true,
              verifiedName: true,
              phone: true,
              avatarUrl: true,
              scoreReputacao: true,
              flagRestricao: true,
            },
          })
        : Promise.resolve([] as ContactSlim[]),
      // DISTINCT ON: 1 query batch — pega o protocolo aberto mais recente por chat
      prisma.$queryRaw<ProtocolSlim[]>`
        SELECT DISTINCT ON ("chatId") "chatId", number, status::text AS status
        FROM protocols
        WHERE "chatId" = ANY(${chatIds}::text[])
          AND status IN ('OPEN','IN_PROGRESS','WAITING_CLIENT','WAITING_AGENT')
        ORDER BY "chatId", "createdAt" DESC
      `,
      // DISTINCT ON: 1 query batch — pega a última msg por chat
      prisma.$queryRaw<LastMsgSlim[]>`
        SELECT DISTINCT ON ("chatId") "chatId", body, "fromMe", status::text AS status, timestamp, type::text AS type, "pushName"
        FROM messages
        WHERE "chatId" = ANY(${chatIds}::text[])
        ORDER BY "chatId", timestamp DESC
      `,
    ])

    // ── Indexa para lookup O(1) ──────────────────────────────────────────────
    const contactById = new Map<string, ContactSlim>(contacts.map((c) => [c.id, c]))
    const protocolByChat = new Map<string, ProtocolSlim>(protocols.map((p) => [p.chatId, p]))
    const lastMsgByChat = new Map<string, LastMsgSlim>(lastMsgs.map((m) => [m.chatId, m]))

    // ── Re-monta a estrutura no formato esperado pelo chat.routes.ts ─────────
    return chats.map((c) => {
      const contact = c.contactId ? contactById.get(c.contactId) ?? null : null
      const protocol = protocolByChat.get(c.id)
      const lastMsg = lastMsgByChat.get(c.id)

      return {
        ...c,
        contact: contact
          ? {
              name: contact.name,
              notify: contact.notify,
              verifiedName: contact.verifiedName,
              phone: contact.phone,
              avatarUrl: contact.avatarUrl,
              scoreReputacao: contact.scoreReputacao,
              flagRestricao: contact.flagRestricao,
            }
          : null,
        protocols: protocol
          ? [{ number: protocol.number, status: protocol.status }]
          : [],
        messages: lastMsg
          ? [{
              body: lastMsg.body,
              fromMe: lastMsg.fromMe,
              status: lastMsg.status,
              timestamp: lastMsg.timestamp,
              type: lastMsg.type,
              pushName: lastMsg.pushName,
            }]
          : [],
      }
    })
  }

  async findById(id: string, tenantId: string) {
    return prisma.chat.findFirst({
      where: { id, tenantId },
      include: {
        contact: true,
        protocols: {
          where: { status: { in: ['OPEN', 'IN_PROGRESS', 'WAITING_CLIENT', 'WAITING_AGENT'] } },
          take: 1,
          orderBy: { createdAt: 'desc' },
        },
      },
    })
  }

  async findByJid(tenantId: string, instanceId: string, jid: string) {
    return prisma.chat.findUnique({
      where: { tenantId_instanceId_jid: { tenantId, instanceId, jid } },
      include: { contact: true },
    })
  }

  async clearUnread(id: string, tenantId: string) {
    return prisma.chat.updateMany({
      where: { id, tenantId },
      data: { unreadCount: 0 },
    })
  }

  async setArchived(id: string, tenantId: string, archived: boolean) {
    return prisma.chat.updateMany({
      where: { id, tenantId },
      data: { isArchived: archived },
    })
  }

  async setPinned(id: string, tenantId: string, pinned: boolean) {
    return prisma.chat.updateMany({
      where: { id, tenantId },
      data: { isPinned: pinned },
    })
  }

  async countUnreadByInstance(tenantId: string, instanceId: string): Promise<number> {
    const result = await prisma.chat.aggregate({
      where: {
        tenantId,
        instanceId,
        isArchived: false,
        NOT: { OR: [
          { jid: { endsWith: '@lid' } },
          { jid: { contains: '@broadcast' } },
          { jid: { endsWith: '@newsletter' } },
        ]}
      },
      _sum: { unreadCount: true },
    })
    return result._sum.unreadCount ?? 0
  }
}
