/**
 * React Query Cache Synchronization
 * Action 2.3.1.1: Create React Query sync utility using BroadcastChannel
 *
 * This utility synchronizes React Query cache updates across browser tabs/windows
 * using BroadcastChannel API. When a query is invalidated or data is updated in one tab,
 * other tabs automatically receive the update and sync their cache.
 *
 * NOTE: This sync mechanism coexists with Zustand state synchronization
 * (see broadcastSync.ts). Both use BroadcastChannel but with different channel
 * names and message formats, so they do not conflict:
 * - Zustand sync: `veza-store-${storeName}` channels with BroadcastMessage format
 * - React Query sync: `veza-react-query-sync` channel with ReactQuerySyncMessage format
 */

import { QueryClient } from '@tanstack/react-query';
import { logger } from './logger';

/**
 * Message format for React Query sync
 */
interface ReactQuerySyncMessage {
  type: 'query-invalidate' | 'query-set-data' | 'mutation-success';
  queryKey: (string | number)[];
  data?: unknown;
  timestamp: number;
  messageId: string;
  tabId?: string; // Identifier for the tab that sent the message
}

/**
 * Options for React Query sync
 */
export interface ReactQuerySyncOptions {
  /** Channel name for BroadcastChannel (default: 'veza-react-query-sync') */
  channelName?: string;
  /** Whether to enable synchronization (default: true) */
  enabled?: boolean;
  /** Function to filter which query updates should be synced */
  shouldSync?: (queryKey: (string | number)[], type: ReactQuerySyncMessage['type']) => boolean;
}

/**
 * Create a BroadcastChannel instance for React Query sync
 */
function createBroadcastChannel(channelName: string): BroadcastChannel | null {
  if (typeof window === 'undefined' || !window.BroadcastChannel) {
    logger.warn(
      '[ReactQuerySync] BroadcastChannel not supported in this environment',
    );
    return null;
  }

  try {
    return new BroadcastChannel(channelName);
  } catch (error) {
    logger.warn('[ReactQuerySync] Failed to create BroadcastChannel', {
      error: error instanceof Error ? error.message : String(error),
      stack: error instanceof Error ? error.stack : undefined,
      channelName,
    });
    return null;
  }
}

/**
 * Generate a unique tab ID for this browser tab
 */
function getTabId(): string {
  if (typeof window === 'undefined') {
    return 'server';
  }

  // Try to get or create a tab ID from sessionStorage
  let tabId = sessionStorage.getItem('veza-tab-id');
  if (!tabId) {
    tabId = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
    sessionStorage.setItem('veza-tab-id', tabId);
  }
  return tabId;
}

/**
 * Generate a unique message ID
 */
function generateMessageId(): string {
  return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}

/**
 * Initialize React Query cache synchronization
 * Action 2.3.1.1: Create React Query sync utility
 *
 * @param queryClient The React Query QueryClient instance
 * @param options Configuration options
 * @returns Cleanup function to stop synchronization
 *
 * @example
 * ```typescript
 * const queryClient = new QueryClient();
 * const cleanup = setupReactQuerySync(queryClient);
 *
 * // Later, to stop syncing:
 * cleanup();
 * ```
 */
export function setupReactQuerySync(
  queryClient: QueryClient,
  options: ReactQuerySyncOptions = {},
): () => void {
  const channelName = options.channelName || 'veza-react-query-sync';
  const enabled = options.enabled !== false;
  const shouldSync = options.shouldSync || (() => true);

  if (!enabled) {
    return () => {}; // No-op cleanup
  }

  const channel = createBroadcastChannel(channelName);
  if (!channel) {
    logger.warn('[ReactQuerySync] BroadcastChannel not available, sync disabled');
    return () => {}; // No-op cleanup
  }

  const tabId = getTabId();
  const processedMessages = new Set<string>();

  // Track if we're currently processing a message to avoid loops
  let isProcessingMessage = false;

  /**
   * Broadcast a query invalidation to other tabs
   */
  function broadcastInvalidation(queryKey: (string | number)[]): void {
    if (isProcessingMessage || !shouldSync(queryKey, 'query-invalidate')) {
      return;
    }

    const message: ReactQuerySyncMessage = {
      type: 'query-invalidate',
      queryKey,
      timestamp: Date.now(),
      messageId: generateMessageId(),
      tabId,
    };

    try {
      if (channel) {
        channel.postMessage(message);
        logger.debug('[ReactQuerySync] Broadcasted query invalidation', {
          queryKey,
          messageId: message.messageId,
        });
      }
    } catch (error) {
      logger.error('[ReactQuerySync] Failed to broadcast invalidation', {
        error: error instanceof Error ? error.message : String(error),
        queryKey,
      });
    }
  }

  /**
   * Broadcast query data update to other tabs
   * Note: This is kept for potential future use but not actively used
   * to avoid performance issues from broadcasting every query update
   * Currently only invalidations and mutations are synced
   */
  // @ts-expect-error - Kept for future use, not currently called
  function broadcastSetData(
    queryKey: (string | number)[],
    data: unknown,
  ): void {
    if (isProcessingMessage || !shouldSync(queryKey, 'query-set-data') || !channel) {
      return;
    }

    const message: ReactQuerySyncMessage = {
      type: 'query-set-data',
      queryKey,
      data,
      timestamp: Date.now(),
      messageId: generateMessageId(),
      tabId,
    };

    try {
      channel.postMessage(message);
      logger.debug('[ReactQuerySync] Broadcasted query data update', {
        queryKey,
        messageId: message.messageId,
      });
    } catch (error) {
      logger.error('[ReactQuerySync] Failed to broadcast data update', {
        error: error instanceof Error ? error.message : String(error),
        queryKey,
      });
    }
  }

  /**
   * Handle incoming messages from other tabs
   */
  function handleMessage(event: MessageEvent<ReactQuerySyncMessage>): void {
    const message = event.data;

    // Action 2.3.1.3: Type guard to ensure this is a valid ReactQuerySyncMessage
    // This prevents processing messages from other sync mechanisms (e.g., Zustand sync)
    if (
      !message ||
      typeof message !== 'object' ||
      !message.type ||
      !Array.isArray(message.queryKey) ||
      typeof message.timestamp !== 'number' ||
      typeof message.messageId !== 'string'
    ) {
      // Not a valid ReactQuerySyncMessage - ignore (likely from a different sync mechanism)
      return;
    }

    // Verify message type is one of the expected React Query sync types
    if (
      message.type !== 'query-invalidate' &&
      message.type !== 'query-set-data' &&
      message.type !== 'mutation-success'
    ) {
      // Not a React Query sync message - ignore
      return;
    }

    // Ignore messages from this tab
    if (message.tabId === tabId) {
      return;
    }

    // Ignore duplicate messages
    if (processedMessages.has(message.messageId)) {
      return;
    }

    // Mark as processed
    processedMessages.add(message.messageId);

    // Clean up old processed message IDs (keep last 1000)
    if (processedMessages.size > 1000) {
      const toDelete = Array.from(processedMessages).slice(0, 500);
      toDelete.forEach((id) => processedMessages.delete(id));
    }

    // Check if we should sync this update
    if (!shouldSync(message.queryKey, message.type)) {
      return;
    }

    isProcessingMessage = true;

    try {
      switch (message.type) {
        case 'query-invalidate':
          queryClient.invalidateQueries({ queryKey: message.queryKey });
          logger.debug('[ReactQuerySync] Invalidated query from other tab', {
            queryKey: message.queryKey,
            messageId: message.messageId,
          });
          break;

        case 'query-set-data':
          if (message.data !== undefined) {
            queryClient.setQueryData(message.queryKey, message.data);
            logger.debug('[ReactQuerySync] Updated query data from other tab', {
              queryKey: message.queryKey,
              messageId: message.messageId,
            });
          }
          break;

        case 'mutation-success':
          // On mutation success, invalidate related queries
          queryClient.invalidateQueries({ queryKey: message.queryKey });
          logger.debug(
            '[ReactQuerySync] Invalidated queries after mutation from other tab',
            {
              queryKey: message.queryKey,
              messageId: message.messageId,
            },
          );
          break;

        default:
          logger.warn('[ReactQuerySync] Unknown message type', {
            type: (message as any).type,
            messageId: message.messageId,
          });
      }
    } catch (error) {
      logger.error('[ReactQuerySync] Error processing sync message', {
        error: error instanceof Error ? error.message : String(error),
        messageId: message.messageId,
        queryKey: message.queryKey,
      });
    } finally {
      // Reset flag after a short delay to allow React Query to process
      setTimeout(() => {
        isProcessingMessage = false;
      }, 50);
    }
  }

  // Listen for messages from other tabs
  channel.addEventListener('message', handleMessage);

  // Subscribe to QueryClient mutations to broadcast invalidations
  const unsubscribeMutations = queryClient.getMutationCache().subscribe(
    (event) => {
      if (!event || !channel) {
        return;
      }

      // Only broadcast successful mutations on 'updated' events
      if (event.type === 'updated' && event.mutation.state.status === 'success') {
        // Get query keys that should be invalidated
        const queryKey = event.mutation.options.mutationKey;
        if (queryKey) {
          const message: ReactQuerySyncMessage = {
            type: 'mutation-success',
            queryKey: queryKey as (string | number)[],
            timestamp: Date.now(),
            messageId: generateMessageId(),
            tabId,
          };

          try {
            channel.postMessage(message);
            logger.debug(
              '[ReactQuerySync] Broadcasted mutation success',
              {
                queryKey,
                messageId: message.messageId,
              },
            );
          } catch (error) {
            logger.error('[ReactQuerySync] Failed to broadcast mutation', {
              error: error instanceof Error ? error.message : String(error),
              queryKey,
            });
          }
        }
      }
    },
  );

  // Subscribe to query cache invalidations to broadcast them
  // We focus on invalidations rather than every data update to avoid performance issues
  const unsubscribeQueries = queryClient.getQueryCache().subscribe((event) => {
    if (event?.type === 'removed' || (event?.type === 'updated' && event.query?.state.isInvalidated)) {
      const queryKey = event.query.queryKey;
      // Broadcast invalidation when queries are removed or invalidated
      broadcastInvalidation(queryKey);
    }
  });

  logger.info('[ReactQuerySync] React Query cache synchronization enabled', {
    channelName,
    tabId,
  });

  // Return cleanup function
  return () => {
    channel.removeEventListener('message', handleMessage);
    unsubscribeMutations();
    unsubscribeQueries();
    channel.close();
    logger.info('[ReactQuerySync] React Query cache synchronization disabled');
  };
}
