import React, { createContext, useContext, useEffect, useRef, useState, useCallback, type JSX } from 'react';
import type { BaseStream } from '../services/Stream';
import type { StreamState } from '../types/stream.types';

interface StreamContextValue {
  stream: BaseStream | null;
  state: StreamState;
  subscribe: <T = any>(
    topic: string,
    callback: (data: T, topic: string) => void,
    qos?: 0 | 1 | 2
  ) => () => void;
  subscribeMany: (
    subscriptions: Array<{ topic: string; qos?: 0 | 1 | 2 }>,
    callback: (data: any, topic: string) => void
  ) => () => void;
  reconnect: () => void;
  getLastMessage: <T = any>(topic: string) => T | undefined;
  isSubscribed: (topic: string) => boolean;
}

const StreamContext = createContext<StreamContextValue | undefined>(undefined);

interface StreamProviderProps {
  stream: BaseStream;
  children: React.ReactNode;
}

export function StreamProvider({ stream, children }: StreamProviderProps): JSX.Element {
  const [state, setState] = useState<StreamState>(stream.getState());
  const streamRef = useRef(stream);

  useEffect(() => {
    const currentStream = streamRef.current;

    const unsubscribeState = currentStream.subscribeToState((newState) => {
      setState(newState);
    });

    return () => {
      unsubscribeState();
    };
  }, []);

  const subscribe = useCallback(<T = any,>(
    topic: string,
    callback: (data: T, topic: string) => void,
    qos: 0 | 1 | 2 = 0
  ) => {
    return streamRef.current.subscribe<T>(topic, callback, qos);
  }, []);

  const subscribeMany = useCallback((
    subscriptions: Array<{ topic: string; qos?: 0 | 1 | 2 }>,
    callback: (data: any, topic: string) => void
  ) => {
    return streamRef.current.subscribeMany(subscriptions, callback);
  }, []);

  const reconnect = useCallback(() => {
    if (streamRef.current) {
      streamRef.current.disconnect();
      streamRef.current.connect();
    }
  }, []);

  const getLastMessage = useCallback(<T = any,>(topic: string) => {
    return streamRef.current.getLastMessage<T>(topic);
  }, []);

  const isSubscribed = useCallback((topic: string) => {
    return streamRef.current.isSubscribed(topic);
  }, []);

  const value: StreamContextValue = {
    stream: streamRef.current,
    state,
    subscribe,
    subscribeMany,
    reconnect,
    getLastMessage,
    isSubscribed,
  };

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

// Main hook
export function useStream(): StreamContextValue {
  const context = useContext(StreamContext);

  if (context === undefined) {
    throw new Error('useStream must be used within a StreamProvider');
  }

  return context;
}

// Topic-specific hook
export function useTopic<T = any>(
  topic: string,
  qos: 0 | 1 | 2 = 0
): {
  lastMessage: T | undefined;
  isSubscribed: boolean;
  subscribe: () => void;
  unsubscribe: () => void;
} {
  const { subscribe: subscribeToTopic, getLastMessage, isSubscribed: checkSubscription } = useStream();
  const [lastMessage, setLastMessage] = useState<T | undefined>(() => getLastMessage<T>(topic));
  const unsubscribeRef = useRef<(() => void) | null>(null);

  useEffect(() => {
    // Cleanup on unmount
    return () => {
      if (unsubscribeRef.current) {
        unsubscribeRef.current();
      }
    };
  }, []);

  const subscribe = useCallback(() => {
    if (!unsubscribeRef.current) {
      unsubscribeRef.current = subscribeToTopic<T>(topic, (data) => {
        setLastMessage(data);
      }, qos);
    }
  }, [topic, qos, subscribeToTopic]);

  const unsubscribe = useCallback(() => {
    if (unsubscribeRef.current) {
      unsubscribeRef.current();
      unsubscribeRef.current = null;
      setLastMessage(undefined);
    }
  }, []);

  return {
    lastMessage,
    isSubscribed: checkSubscription(topic),
    subscribe,
    unsubscribe,
  };
}

// Multiple topics hook
export function useTopics<T extends Record<string, any>>(
  topics: Array<{ topic: keyof T & string; qos?: 0 | 1 | 2 }>
): {
  lastMessages: Partial<T>;
  isSubscribed: Record<keyof T, boolean>;
  subscribe: () => void;
  unsubscribe: () => void;
} {
  const { subscribe: subscribeToTopic, getLastMessage, isSubscribed: checkSubscription } = useStream();
  const [lastMessages, setLastMessages] = useState<Partial<T>>(() => {
    const initial: Partial<T> = {};
    topics.forEach(({ topic }) => {
      initial[topic as keyof T] = getLastMessage(topic as string);
    });
    return initial;
  });

  const unsubscribersRef = useRef<Map<keyof T, () => void>>(new Map());

  useEffect(() => {
    return () => {
      unsubscribersRef.current.forEach(unsub => unsub());
    };
  }, []);

  const subscribe = useCallback(() => {
    topics.forEach(({ topic, qos = 0 }) => {
      if (!unsubscribersRef.current.has(topic)) {
        const unsub = subscribeToTopic(topic as string, (data) => {
          setLastMessages(prev => ({ ...prev, [topic]: data }));
        }, qos);
        unsubscribersRef.current.set(topic, unsub);
      }
    });
  }, [topics, subscribeToTopic]);

  const unsubscribe = useCallback(() => {
    unsubscribersRef.current.forEach(unsub => unsub());
    unsubscribersRef.current.clear();
    setLastMessages({});
  }, []);

  const isSubscribedRecord = topics.reduce((acc, { topic }) => {
    acc[topic as keyof T] = checkSubscription(topic as string);
    return acc;
  }, {} as Record<keyof T, boolean>);

  return {
    lastMessages,
    isSubscribed: isSubscribedRecord,
    subscribe,
    unsubscribe,
  };
}

// Connection state hook
export function useStreamConnection() {
  const { state } = useStream();
  return {
    isConnected: state.isConnected,
    isConnecting: state.isConnecting,
    error: state.error,
    subscriptions: Array.from(state.subscriptions.keys()),
  };
}
