import React, { createContext, useContext, useEffect, useRef, useState, useCallback, useMemo } from 'react';
import { AppState, type AppStateStatus } from 'react-native';
import { useDubs } from '../provider';
import { ChatSocket } from './socket';
import type {
  ChatMessage,
  OnlineUser,
  ChatConnectionStatus,
  Conversation,
  FriendUser,
  FriendRequest,
  SentFriendRequest,
  DirectMessage,
  ChatNotification,
} from './types';
import type { WhatsNewPost } from '../types';

export interface ChatContextValue {
  /** The underlying socket manager */
  socket: ChatSocket;
  /** Connection status */
  status: ChatConnectionStatus;
  /** Global chat messages (newest last) */
  messages: ChatMessage[];
  /** Currently online users */
  onlineUsers: OnlineUser[];
  /** Online user count */
  onlineCount: number;
  /** Unread notification count */
  unreadCount: number;
  /** DM conversations */
  conversations: Conversation[];
  /** Friends list */
  friends: FriendUser[];
  /** Pending friend requests (received) */
  pendingRequests: FriendRequest[];
  /** Pending friend requests this user has sent (still awaiting response) */
  sentFriendRequests: SentFriendRequest[];
  /** Reload messages from REST */
  refreshMessages: () => Promise<void>;
  /** Reload conversations from REST */
  refreshConversations: () => Promise<void>;
  /** Reload friends from REST */
  refreshFriends: () => Promise<void>;
  /** Reload pending friend requests (received) from REST */
  refreshPendingRequests: () => Promise<void>;
  /** Reload pending friend requests (sent) from REST */
  refreshSentFriendRequests: () => Promise<void>;
  /** What's New posts for this app */
  whatsNewPosts: WhatsNewPost[];
  /** Number of unread What's New posts */
  whatsNewUnreadCount: number;
  /** Reload What's New posts + unread count from REST */
  refreshWhatsNew: () => Promise<void>;
}

const ChatContext = createContext<ChatContextValue | null>(null);

export interface ChatProviderProps {
  children: React.ReactNode;
  /** Set to false to disable auto-connect on mount. Default: true */
  autoConnect?: boolean;
}

/**
 * Provides chat, DM, and social context to child components.
 * Must be rendered inside a <DubsProvider>.
 */
export function ChatProvider({ children, autoConnect = true }: ChatProviderProps) {
  const { client } = useDubs();
  const socketRef = useRef(new ChatSocket());

  const [status, setStatus] = useState<ChatConnectionStatus>('disconnected');
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [onlineUsers, setOnlineUsers] = useState<OnlineUser[]>([]);
  const [onlineCount, setOnlineCount] = useState(0);
  const [unreadCount, setUnreadCount] = useState(0);
  const [conversations, setConversations] = useState<Conversation[]>([]);
  const [friends, setFriends] = useState<FriendUser[]>([]);
  const [pendingRequests, setPendingRequests] = useState<FriendRequest[]>([]);
  const [sentFriendRequests, setSentFriendRequests] = useState<SentFriendRequest[]>([]);
  const [whatsNewPosts, setWhatsNewPosts] = useState<WhatsNewPost[]>([]);
  const [whatsNewUnreadCount, setWhatsNewUnreadCount] = useState(0);

  // ── REST loaders ──

  const refreshMessages = useCallback(async () => {
    try {
      const res = await client.getChatMessages({ limit: 30 });
      // The dev API returns messages oldest-first (chatService applies a
      // .reverse() for legacy chat-order callers). Inverted chat FlatLists
      // — and onNewMessage's prepend below — assume newest-first. Flip
      // here so the internal store is consistent everywhere.
      setMessages([...res.messages].reverse());
    } catch (err) {
      console.error('[Dubs:ChatProvider] Failed to load messages:', err);
    }
  }, [client]);

  const refreshConversations = useCallback(async () => {
    try {
      const res = await client.getConversations();
      setConversations(res.conversations);
    } catch (_) {
      // DM service may not be deployed yet — silent fail
    }
  }, [client]);

  const refreshFriends = useCallback(async () => {
    try {
      const res = await client.getFriends();
      setFriends(res.friends);
    } catch (_) {
      // Social service may not be deployed yet — silent fail
    }
  }, [client]);

  const refreshPendingRequests = useCallback(async () => {
    try {
      const res = await client.getPendingFriendRequests();
      setPendingRequests(res.requests);
    } catch (_) {
      // Social service may not be deployed yet — silent fail
    }
  }, [client]);

  const refreshSentFriendRequests = useCallback(async () => {
    try {
      const res = await client.getSentFriendRequests();
      setSentFriendRequests(res.requests);
    } catch (_) {
      // Server may not yet expose /social/friend-requests/sent — silent fail
    }
  }, [client]);

  const refreshWhatsNew = useCallback(async () => {
    try {
      const res = await client.getWhatsNewPosts();
      setWhatsNewPosts(res.posts);
      // Count unread from the same payload (avoids a second roundtrip)
      const unread = res.posts.filter((p: WhatsNewPost) => !p.is_read).length;
      setWhatsNewUnreadCount(unread);
    } catch (_) {
      // Server may not yet expose /whats-new endpoints — silent fail
    }
  }, [client]);

  // ── Socket setup ──

  useEffect(() => {
    const token = client.getToken();
    if (!autoConnect || !token) return;

    const chatSocket = socketRef.current;

    // Derive host from client baseUrl: strip /api/developer/v1
    const baseUrl = (client as any).baseUrl as string;
    const host = new URL(baseUrl).origin;

    chatSocket.setListeners({
      // Refetch the message list whenever the socket reaches 'connected'.
      // Covers both the initial connect AND every reconnect (app resumed
      // from background, transient network blip, server restart). Without
      // this, the provider's `messages` state is frozen at whatever it was
      // when the app last connected — Socket.IO's auto-reconnect resumes
      // delivery of *new* events but does not replay the gap.
      onConnectionChange: (next) => {
        setStatus(next);
        if (next === 'connected') {
          refreshMessages().catch(() => {});
          refreshConversations().catch(() => {});
        }
      },
      // Global chat
      onNewMessage: (msg) => {
        setMessages((prev) => {
          // Deduplicate by id
          if (prev.some((m) => m.id === msg.id)) return prev;
          // Prepend — newest-first for inverted FlatList
          return [msg, ...prev];
        });
      },
      onOnlineUsers: setOnlineUsers,
      onOnlineCount: setOnlineCount,
      onUnreadCount: setUnreadCount,
      // Notifications trigger conversation refresh
      onDMNotification: () => {
        refreshConversations();
      },
      onNotification: (n: ChatNotification) => {
        setUnreadCount((prev) => prev + 1);
        // Refresh relevant lists on social notifications
        if (n.type === 'friend_request') refreshPendingRequests();
        if (n.type === 'friend_request_accepted') {
          refreshFriends();
          refreshSentFriendRequests();
        }
        if (n.type === 'friend_request_declined') refreshSentFriendRequests();
        // What's New posts: a developer published a new one for this app
        if (n.type === 'whats_new') refreshWhatsNew();
      },
      // These events now fan out to BOTH parties (server emits to actor's
      // own room as well), so each handler refreshes every list that could
      // be affected — works whether this client is the actor or the target.
      onFriendRequestAccepted: () => {
        refreshFriends();
        refreshPendingRequests();
        refreshSentFriendRequests();
      },
      onFriendRequestDeclined: () => {
        refreshPendingRequests();
        refreshSentFriendRequests();
      },
      onFriendRequestCancelled: () => {
        refreshPendingRequests();
        refreshSentFriendRequests();
      },
      onFriendRemoved: () => refreshFriends(),
    });

    chatSocket.connect({ host, token });

    // Load initial data — friends/social loaded silently (may not be deployed yet)
    refreshMessages();
    refreshFriends().catch(() => {});
    refreshPendingRequests().catch(() => {});
    refreshSentFriendRequests().catch(() => {});
    refreshWhatsNew().catch(() => {});

    return () => {
      chatSocket.disconnect();
    };
  }, [client, autoConnect, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew]);

  // ── Reconnect on app foreground ──

  useEffect(() => {
    const handleAppState = (nextState: AppStateStatus) => {
      if (nextState === 'active') {
        const chatSocket = socketRef.current;
        if (!chatSocket.isConnected()) {
          const token = client.getToken();
          if (token) {
            const baseUrl = (client as any).baseUrl as string;
            const host = new URL(baseUrl).origin;
            chatSocket.connect({ host, token });
          }
        }
        // Refresh all key data to catch up on socket events that fired
        // while the app was backgrounded (declines, accepts, cancels,
        // new requests, friend removals, etc.)
        refreshMessages();
        refreshConversations().catch(() => {});
        refreshFriends().catch(() => {});
        refreshPendingRequests().catch(() => {});
        refreshSentFriendRequests().catch(() => {});
        refreshWhatsNew().catch(() => {});
      }
    };

    const sub = AppState.addEventListener('change', handleAppState);
    return () => sub.remove();
  }, [client, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew]);

  const value = useMemo<ChatContextValue>(
    () => ({
      socket: socketRef.current,
      status,
      messages,
      onlineUsers,
      onlineCount,
      unreadCount,
      conversations,
      friends,
      pendingRequests,
      sentFriendRequests,
      whatsNewPosts,
      whatsNewUnreadCount,
      refreshMessages,
      refreshConversations,
      refreshFriends,
      refreshPendingRequests,
      refreshSentFriendRequests,
      refreshWhatsNew,
    }),
    [status, messages, onlineUsers, onlineCount, unreadCount, conversations, friends, pendingRequests, sentFriendRequests, whatsNewPosts, whatsNewUnreadCount, refreshMessages, refreshConversations, refreshFriends, refreshPendingRequests, refreshSentFriendRequests, refreshWhatsNew],
  );

  return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
}

/** Access the chat context. Must be used inside <ChatProvider>. */
export function useChatContext(): ChatContextValue {
  const ctx = useContext(ChatContext);
  if (!ctx) {
    throw new Error('useChatContext must be used inside a <ChatProvider>');
  }
  return ctx;
}
