/**
 * chatStore.ts — Store de Inbox: chats e mensagens.
 * Instâncias foram extraídas para instanceStore.ts.
 * Usa Zustand + Immer para mutações diretas e legíveis.
 */
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
import { socketService } from '../services/socketService'
import { chatApi, messageApi } from '../services/chatApiService'

// ─── Mapper: resposta aninhada do backend → Chat plano do store ────────────────

function mapBackendChat(raw: any): Chat {
  return {
    id:               raw.id,
    tenantId:         raw.tenantId ?? '',
    instanceId:       raw.instanceId,
    jid:              raw.jid,
    name:             raw.name ?? null,
    unreadCount:      raw.unreadCount ?? 0,
    lastMessageAt:    raw.lastMessageAt ?? null,
    createdAt:        raw.createdAt ?? null,
    isPinned:         raw.isPinned ?? false,
    isArchived:       raw.isArchived ?? false,
    contactName:          raw.contact?.name ?? raw.contactName ?? null,
    contactAvatarUrl:     raw.contact?.avatarUrl ?? raw.contactAvatarUrl ?? null,
    contactAvatarVersion: raw.contact?.avatarVersion ?? raw.contactAvatarVersion ?? null,
    contactPhone:         raw.contact?.phone ?? raw.contactPhone ?? null,
    scoreReputacao:   raw.contact?.scoreReputacao ?? raw.scoreReputacao ?? 0,
    flagRestricao:    raw.contact?.flagRestricao ?? raw.flagRestricao ?? false,
    lastMessageBody:  raw.lastMessage?.body ?? raw.lastMessageBody ?? null,
    lastMessageFromMe: raw.lastMessage?.fromMe ?? raw.lastMessageFromMe ?? false,
    lastMessageStatus: raw.lastMessage?.status ?? raw.lastMessageStatus ?? null,
    lastMessageType:  raw.lastMessage?.type ?? raw.lastMessageType ?? null,
    lastMessageSender: raw.lastMessage?.pushName ?? raw.lastMessageSender ?? null,
    protocolNumber:   raw.protocol?.number ?? raw.protocolNumber ?? null,
    botPaused:        raw.botPaused ?? false,
  }
}

// ─── Tipos ────────────────────────────────────────────────────────────────────

export interface Chat {
  id: string
  tenantId: string
  instanceId: string
  jid: string
  name: string | null         // subject do grupo; null para individuais
  unreadCount: number
  lastMessageAt: string | null
  createdAt: string | null     // quando o chat foi registrado (fallback de ordenação)
  isPinned: boolean
  isArchived: boolean
  contactName: string | null
  contactAvatarUrl: string | null
  contactAvatarVersion: string | null
  contactPhone: string | null
  scoreReputacao: number
  flagRestricao: boolean
  lastMessageBody: string | null
  lastMessageFromMe: boolean
  lastMessageStatus: string | null
  lastMessageType: string | null
  lastMessageSender: string | null  // pushName do remetente (grupos: quem enviou a última msg)
  protocolNumber: string | null
  botPaused: boolean
}

export interface Message {
  id: string
  chatId: string
  messageId: string
  fromMe: boolean
  type: string
  body: string | null
  mediaUrl: string | null
  mimeType: string | null
  fileName: string | null
  replyToId: string | null
  replyTo?: {
    id: string
    messageId: string
    body: string | null
    fromMe: boolean
    type: string
    mediaUrl: string | null
  } | null
  status: 'PENDING' | 'SENT' | 'DELIVERED' | 'READ' | 'FAILED'
  timestamp: string
  pushName?: string | null
  senderJid?: string | null
}

// ─── Estado ───────────────────────────────────────────────────────────────────

interface ChatState {
  chats: Chat[]
  chatsLoading: boolean
  activeChatId: string | null
  messages: Record<string, Message[]>
  messagesLoading: boolean

  // Sync
  setChats: (chats: Chat[]) => void
  setChatsLoading: (v: boolean) => void
  upsertChat: (chat: Chat) => void
  patchChat: (chatId: string, patch: Partial<Chat>) => void
  setActiveChat: (chatId: string | null) => void
  clearUnread: (chatId: string) => void
  setMessages: (chatId: string, msgs: Message[]) => void
  setMessagesLoading: (v: boolean) => void
  appendMessage: (msg: Message) => void
  patchMessage: (msgId: string, patch: Partial<Message>) => void
  updateMediaReady: (msgId: string, mediaUrl: string) => void

  // Async
  loadChats: (instanceId: string, opts?: { search?: string; archived?: boolean }) => Promise<void>
  loadMessages: (chatId: string, opts?: { before?: string }) => Promise<{ hasMore: boolean }>
  selectChat: (chatId: string) => Promise<void>
  sendMessage: (instanceId: string, jid: string, text: string, replyToMessageId?: string, mentions?: string[]) => Promise<void>

  // Socket
  initSocketListeners: () => void
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function chatSortKey(c: Chat): number {
  // WhatsApp ordena por: timestamp da última msg; se não houver msg, usa hora de criação do chat
  if (c.lastMessageAt) return new Date(c.lastMessageAt).getTime()
  if (c.createdAt) return new Date(c.createdAt).getTime()
  return 0
}

function sortChats(chats: Chat[]): Chat[] {
  return [...chats].sort((a, b) => {
    // Fixados sempre primeiro (mesma lógica do WhatsApp oficial)
    if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1
    return chatSortKey(b) - chatSortKey(a)
  })
}

// ─── Store ────────────────────────────────────────────────────────────────────

// ─── Cache local (localStorage) — UX instantâneo no reload ─────────────────────
// Persiste os primeiros N chats por instância para hidratar a UI antes do fetch.
// Chave: `inboxCache:v1:${instanceId}:${archived ? '1' : '0'}`
// TTL implícito: validamos pela data salva e descartamos se > 7 dias.
const INBOX_CACHE_PREFIX = 'inboxCache:v1'
const INBOX_CACHE_LIMIT = 10
const INBOX_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000

interface InboxCacheEntry {
  savedAt: number
  chats: Chat[]
}

function inboxCacheKey(instanceId: string, archived: boolean): string {
  return `${INBOX_CACHE_PREFIX}:${instanceId}:${archived ? '1' : '0'}`
}

function readInboxCache(instanceId: string, archived: boolean): Chat[] | null {
  if (typeof window === 'undefined') return null
  try {
    const raw = localStorage.getItem(inboxCacheKey(instanceId, archived))
    if (!raw) return null
    const parsed = JSON.parse(raw) as InboxCacheEntry
    if (!parsed || !Array.isArray(parsed.chats)) return null
    if (Date.now() - parsed.savedAt > INBOX_CACHE_MAX_AGE_MS) return null
    return parsed.chats
  } catch { return null }
}

function writeInboxCache(instanceId: string, archived: boolean, chats: Chat[]): void {
  if (typeof window === 'undefined') return
  try {
    const slim = chats.slice(0, INBOX_CACHE_LIMIT)
    const entry: InboxCacheEntry = { savedAt: Date.now(), chats: slim }
    localStorage.setItem(inboxCacheKey(instanceId, archived), JSON.stringify(entry))
  } catch { /* quota exceeded — ignora */ }
}

export const useChatStore = create<ChatState>()(
  immer((set, get) => ({
    chats: [],
    chatsLoading: false,
    activeChatId: null,
    messages: {},
    messagesLoading: false,

    // ── Chats ─────────────────────────────────────────────────────────────────

    setChats: (chats) =>
      set((s) => { s.chats = sortChats(chats) }),

    setChatsLoading: (v) =>
      set((s) => { s.chatsLoading = v }),

    upsertChat: (chat) =>
      set((s) => {
        const idx = s.chats.findIndex((c) => c.id === chat.id)
        if (idx !== -1) s.chats[idx] = chat
        else s.chats.unshift(chat)
        s.chats = sortChats(s.chats as Chat[])
      }),

    patchChat: (chatId, patch) =>
      set((s) => {
        const idx = s.chats.findIndex((c) => c.id === chatId)
        if (idx !== -1) {
          Object.assign(s.chats[idx], patch)
          s.chats = sortChats(s.chats as Chat[])
        }
      }),

    setActiveChat: (chatId) =>
      set((s) => { s.activeChatId = chatId }),

    clearUnread: (chatId) =>
      set((s) => {
        const c = s.chats.find((c) => c.id === chatId)
        if (c) c.unreadCount = 0
      }),

    // ── Mensagens ─────────────────────────────────────────────────────────────

    setMessages: (chatId, msgs) =>
      set((s) => { s.messages[chatId] = msgs }),

    setMessagesLoading: (v) =>
      set((s) => { s.messagesLoading = v }),

    appendMessage: (msg) =>
      set((s) => {
        const list = s.messages[msg.chatId]
        if (!list) { s.messages[msg.chatId] = [msg]; return }
        const exists = list.some((m) => m.id === msg.id || m.messageId === msg.messageId)
        if (!exists) list.push(msg)
      }),

    patchMessage: (msgId, patch) =>
      set((s) => {
        for (const list of Object.values(s.messages)) {
          const idx = list.findIndex((m) => m.id === msgId || m.messageId === msgId)
          if (idx !== -1) { Object.assign(list[idx], patch); break }
        }
      }),

    updateMediaReady: (msgId, mediaUrl) =>
      set((s) => {
        for (const list of Object.values(s.messages)) {
          const msg = list.find((m) => m.id === msgId)
          if (msg) { msg.mediaUrl = mediaUrl; break }
        }
      }),

    // ── Async ─────────────────────────────────────────────────────────────────

    loadChats: async (instanceId, opts = {}) => {
      // Hidratação instantânea: se temos cache local válido para esta instância,
      // mostra os 10 chats salvos imediatamente enquanto a request real roda.
      // Evita tela em branco ou skeleton de 15s+ em redes lentas.
      const archivedFlag = opts.archived ?? false
      const hasSearch = !!opts.search
      const current = get().chats
      if (!hasSearch) {
        const sameInstance = current.length > 0 && current[0]?.instanceId === instanceId
        if (!sameInstance) {
          const cached = readInboxCache(instanceId, archivedFlag)
          if (cached && cached.length > 0) {
            set((s) => { s.chats = sortChats(cached); s.chatsLoading = true })
          } else {
            set((s) => { s.chatsLoading = true })
          }
        } else {
          set((s) => { s.chatsLoading = true })
        }
      } else {
        set((s) => { s.chatsLoading = true })
      }

      try {
        const raw = await chatApi.list(instanceId, opts)
        const data: Chat[] = (Array.isArray(raw) ? raw : []).map(mapBackendChat)
        const sorted = sortChats(data)
        set((s) => { s.chats = sorted; s.chatsLoading = false })
        if (!hasSearch) writeInboxCache(instanceId, archivedFlag, sorted)
      } catch {
        set((s) => { s.chatsLoading = false })
      }
    },

    loadMessages: async (chatId, opts = {}) => {
      console.log('[chatStore] loadMessages chamado:', chatId, opts)
      set((s) => { s.messagesLoading = true })
      try {
        const res = await messageApi.list(chatId, opts)
        console.log('[chatStore] API retornou:', { hasMore: res.hasMore, count: res.messages?.length, raw: typeof res })
        const { messages, hasMore } = res
        if (!Array.isArray(messages)) {
          console.error('[chatStore] messages NÃO é array!', messages)
          set((s) => { s.messagesLoading = false })
          return { hasMore: false }
        }
        set((s) => {
          if (opts.before) {
            const existing = s.messages[chatId] ?? []
            const ids = new Set(existing.map((m) => m.id))
            s.messages[chatId] = [...messages.filter((m: Message) => !ids.has(m.id)), ...existing]
          } else {
            s.messages[chatId] = messages
          }
          s.messagesLoading = false
        })
        console.log('[chatStore] messages armazenadas:', get().messages[chatId]?.length)
        return { hasMore }
      } catch (err) {
        console.error('[chatStore] loadMessages ERRO:', err)
        set((s) => { s.messagesLoading = false })
        return { hasMore: false }
      }
    },

    selectChat: async (chatId) => {
      console.log('[chatStore] selectChat:', chatId)
      const prev = get().activeChatId
      if (prev && prev !== chatId) socketService.leaveChat(prev)
      set((s) => { s.activeChatId = chatId })
      socketService.joinChat(chatId)
      get().clearUnread(chatId)
      chatApi.markRead(chatId).catch(() => {})
      await get().loadMessages(chatId)
      console.log('[chatStore] selectChat completo. Messages:', get().messages[chatId]?.length)
    },

    sendMessage: async (instanceId, jid, text, replyToMessageId, mentions) => {
      const chatId = get().chats.find((c) => c.jid === jid && c.instanceId === instanceId)?.id
      if (!chatId) return

      // Resolve replyTo a partir das mensagens já no store
      const replyToMsg = replyToMessageId
        ? get().messages[chatId]?.find((m) => m.messageId === replyToMessageId) ?? null
        : null

      const optimistic: Message = {
        id: `opt-${Date.now()}`,
        chatId,
        messageId: `opt-${Date.now()}`,
        fromMe: true,
        type: 'TEXT',
        body: text,
        mediaUrl: null,
        mimeType: null,
        fileName: null,
        replyToId: replyToMsg?.id ?? null,
        replyTo: replyToMsg
          ? { id: replyToMsg.id, messageId: replyToMsg.messageId, body: replyToMsg.body, fromMe: replyToMsg.fromMe, type: replyToMsg.type, mediaUrl: replyToMsg.mediaUrl }
          : null,
        status: 'PENDING',
        timestamp: new Date().toISOString(),
      }
      get().appendMessage(optimistic)

      try {
        const saved = await messageApi.send(instanceId, jid, text, replyToMessageId, mentions)
        get().patchMessage(optimistic.id, { id: saved.id, messageId: saved.messageId, status: 'SENT' })
        get().patchChat(chatId, {
          lastMessageBody: text,
          lastMessageFromMe: true,
          lastMessageAt: saved.timestamp,
          lastMessageStatus: 'SENT',
          lastMessageType: 'TEXT',
        })
      } catch {
        get().patchMessage(optimistic.id, { status: 'FAILED' })
      }
    },

    // ── Socket ────────────────────────────────────────────────────────────────

    initSocketListeners: () => {
      // IMPORTANTE: sempre usar get() dentro dos callbacks para ler estado atual.
      // Capturar `const store = get()` fora criaria uma closure stale.

      socketService.on('message:new', (msg: Message) => {
        console.log('[socket] message:new recebida:', { chatId: msg.chatId, body: msg.body?.slice(0, 40), fromMe: msg.fromMe })
        get().appendMessage(msg)
        const current = get()
        const isActive = current.activeChatId === msg.chatId
        current.patchChat(msg.chatId, {
          lastMessageBody: msg.body,
          lastMessageFromMe: msg.fromMe,
          lastMessageAt: msg.timestamp,
          lastMessageStatus: msg.status,
          lastMessageType: msg.type,
          lastMessageSender: msg.fromMe ? null : (msg.pushName ?? null),
          unreadCount: msg.fromMe || isActive
            ? 0
            : (current.chats.find((c) => c.id === msg.chatId)?.unreadCount ?? 0) + 1,
        })
      })

      socketService.on('message:media_ready', ({ messageId, mediaUrl }: { messageId: string; mediaUrl: string }) => {
        get().updateMediaReady(messageId, mediaUrl)
      })

      socketService.on('message:status', ({ messageId, status }: { messageId: string; status: Message['status'] }) => {
        get().patchMessage(messageId, { status })
      })

      socketService.on('chat:upsert', (raw: any) => {
        get().upsertChat(mapBackendChat(raw))
      })

      socketService.on('bot:escalated', ({ chatId }: { chatId: string }) => {
        get().patchChat(chatId, { botPaused: true })
      })

      socketService.on('contact:avatar', ({ instanceId, jid, avatarUrl }: { instanceId: string; jid: string; avatarUrl: string }) => {
        set((s) => {
          const chat = s.chats.find((c) => c.instanceId === instanceId && c.jid === jid)
          if (chat) chat.contactAvatarUrl = avatarUrl
        })
      })

      // Reconciliação de nomes concluída — re-busca lista de chats
      socketService.on('chats:refresh', ({ instanceId }: { instanceId: string }) => {
        const chats = get().chats
        if (chats.length > 0 && chats[0]?.instanceId === instanceId) {
          get().loadChats(instanceId)
        }
      })

      // Reconciliação via ContactHandler (pós-sync)
      socketService.on('contacts:reconciled', ({ instanceId }: { instanceId: string }) => {
        const chats = get().chats
        if (chats.length > 0 && chats[0]?.instanceId === instanceId) {
          get().loadChats(instanceId)
        }
      })
    },
  }))
)
