/**
 * Socket.io Context Provider for LocalGreenChain
 *
 * Provides socket connection state to the entire application.
 */

import React, { createContext, useContext, useEffect, useState, useCallback, useRef, ReactNode } from 'react';
import { getSocketClient, RealtimeSocketClient } from './socketClient';
import type {
  ConnectionStatus,
  TransparencyEvent,
  RoomType,
  TransparencyEventType,
  ConnectionMetrics,
  RealtimeNotification,
} from './types';
import { toFeedItem } from './events';

/**
 * Socket context value type
 */
interface SocketContextValue {
  // Connection state
  status: ConnectionStatus;
  isConnected: boolean;
  metrics: ConnectionMetrics;

  // Events
  events: TransparencyEvent[];
  latestEvent: TransparencyEvent | null;

  // Notifications
  notifications: RealtimeNotification[];
  unreadCount: number;

  // Actions
  connect: () => void;
  disconnect: () => void;
  joinRoom: (room: RoomType) => Promise<boolean>;
  leaveRoom: (room: RoomType) => Promise<boolean>;
  subscribeToTypes: (types: TransparencyEventType[]) => Promise<boolean>;
  clearEvents: () => void;
  markNotificationRead: (id: string) => void;
  dismissNotification: (id: string) => void;
  markAllRead: () => void;
}

const SocketContext = createContext<SocketContextValue | null>(null);

/**
 * Provider props
 */
interface SocketProviderProps {
  children: ReactNode;
  userId?: string;
  autoConnect?: boolean;
  maxEvents?: number;
  maxNotifications?: number;
}

/**
 * Convert event to notification
 */
function eventToNotification(event: TransparencyEvent): RealtimeNotification {
  const feedItem = toFeedItem(event);

  let notificationType: RealtimeNotification['type'] = 'info';
  if (event.priority === 'CRITICAL') notificationType = 'error';
  else if (event.priority === 'HIGH') notificationType = 'warning';
  else if (event.type.includes('error')) notificationType = 'error';
  else if (event.type.includes('completed') || event.type.includes('verified')) notificationType = 'success';

  return {
    id: event.id,
    type: notificationType,
    title: feedItem.formatted.title,
    message: feedItem.formatted.description,
    timestamp: feedItem.timestamp,
    eventType: event.type,
    data: event.data,
    read: false,
    dismissed: false,
  };
}

/**
 * Socket Provider component
 */
export function SocketProvider({
  children,
  userId,
  autoConnect = true,
  maxEvents = 100,
  maxNotifications = 50,
}: SocketProviderProps) {
  const [status, setStatus] = useState<ConnectionStatus>('disconnected');
  const [events, setEvents] = useState<TransparencyEvent[]>([]);
  const [latestEvent, setLatestEvent] = useState<TransparencyEvent | null>(null);
  const [notifications, setNotifications] = useState<RealtimeNotification[]>([]);
  const [metrics, setMetrics] = useState<ConnectionMetrics>({
    status: 'disconnected',
    eventsReceived: 0,
    reconnectAttempts: 0,
    rooms: [],
  });

  const clientRef = useRef<RealtimeSocketClient | null>(null);

  // Initialize client
  useEffect(() => {
    if (typeof window === 'undefined') return;

    const client = getSocketClient({ auth: { userId } });
    clientRef.current = client;

    // Set up listeners
    const unsubStatus = client.onStatusChange((newStatus) => {
      setStatus(newStatus);
      setMetrics(client.getMetrics());
    });

    const unsubEvent = client.onEvent((event) => {
      setLatestEvent(event);
      setEvents((prev) => [event, ...prev].slice(0, maxEvents));
      setMetrics(client.getMetrics());

      // Create notification for important events
      if (event.priority === 'HIGH' || event.priority === 'CRITICAL') {
        const notification = eventToNotification(event);
        setNotifications((prev) => [notification, ...prev].slice(0, maxNotifications));
      }
    });

    // Auto connect
    if (autoConnect) {
      client.connect();
    }

    // Initial metrics
    setMetrics(client.getMetrics());

    return () => {
      unsubStatus();
      unsubEvent();
    };
  }, [autoConnect, userId, maxEvents, maxNotifications]);

  const connect = useCallback(() => {
    clientRef.current?.connect();
  }, []);

  const disconnect = useCallback(() => {
    clientRef.current?.disconnect();
  }, []);

  const joinRoom = useCallback(async (room: RoomType) => {
    return clientRef.current?.joinRoom(room) ?? false;
  }, []);

  const leaveRoom = useCallback(async (room: RoomType) => {
    return clientRef.current?.leaveRoom(room) ?? false;
  }, []);

  const subscribeToTypes = useCallback(async (types: TransparencyEventType[]) => {
    return clientRef.current?.subscribeToTypes(types) ?? false;
  }, []);

  const clearEvents = useCallback(() => {
    setEvents([]);
    setLatestEvent(null);
  }, []);

  const markNotificationRead = useCallback((id: string) => {
    setNotifications((prev) =>
      prev.map((n) => (n.id === id ? { ...n, read: true } : n))
    );
  }, []);

  const dismissNotification = useCallback((id: string) => {
    setNotifications((prev) =>
      prev.map((n) => (n.id === id ? { ...n, dismissed: true } : n))
    );
  }, []);

  const markAllRead = useCallback(() => {
    setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
  }, []);

  const unreadCount = notifications.filter((n) => !n.read && !n.dismissed).length;

  const value: SocketContextValue = {
    status,
    isConnected: status === 'connected',
    metrics,
    events,
    latestEvent,
    notifications: notifications.filter((n) => !n.dismissed),
    unreadCount,
    connect,
    disconnect,
    joinRoom,
    leaveRoom,
    subscribeToTypes,
    clearEvents,
    markNotificationRead,
    dismissNotification,
    markAllRead,
  };

  return (
    <SocketContext.Provider value={value}>
      {children}
    </SocketContext.Provider>
  );
}

/**
 * Hook to use socket context
 */
export function useSocketContext(): SocketContextValue {
  const context = useContext(SocketContext);
  if (!context) {
    throw new Error('useSocketContext must be used within a SocketProvider');
  }
  return context;
}

/**
 * Hook to optionally use socket context (returns null if not in provider)
 */
export function useOptionalSocketContext(): SocketContextValue | null {
  return useContext(SocketContext);
}

export default SocketContext;
