'use client';

import React, { createContext, useContext, useEffect, useRef, useState, useCallback } from 'react';
import mqtt from 'mqtt';
import type { ConversationMeta } from '@claw/core/lib/types/memory';
import type { PendingMessage } from '@claw/core/lib/types/session';
import { logger } from '@claw/core/lib/logger';
import { useTenant } from './TenantProvider';

export interface RealtimeMessage {
  'detail-type': string;
  detail: Record<string, unknown>;
  [key: string]: unknown;
}

type MessageCallback = (topic: string, payload: RealtimeMessage) => void;

interface Subscription {
  topics: string[];
  compiledTopics: RegExp[];
  callback: MessageCallback;
}

interface DashboardConfig {
  app: string;
  stage: string;
  realtime: {
    url: string | null;
    authorizer?: string;
  };
}

export interface RealtimeContextType {
  isConnected: boolean;
  error: Error | null;
  userId: string | null;
  subscribe: (topics: string[], callback: MessageCallback) => () => void;
  sessions: ConversationMeta[];
  pendingMessages: PendingMessage[];
  setPendingMessages: React.Dispatch<React.SetStateAction<PendingMessage[]>>;
  fetchSessions: () => Promise<void>;
  isLive: boolean;
}

const RealtimeContext = createContext<RealtimeContextType | null>(null);

export function useRealtimeContext() {
  const context = useContext(RealtimeContext);
  if (!context) {
    throw new Error('useRealtimeContext must be used within a RealtimeProvider');
  }
  return context;
}

const STABLE_USER_ID = 'dashboard-user';

export function RealtimeProvider({ children }: { children: React.ReactNode }) {
  const [isConnected, setIsConnected] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const [sessions, setSessions] = useState<ConversationMeta[]>([]);
  const [pendingMessages, setPendingMessages] = useState<PendingMessage[]>([]);
  const { activeWorkspaceId } = useTenant();

  const mqttClientRef = useRef<mqtt.MqttClient | null>(null);
  const isUnmountedRef = useRef<boolean>(false);
  const subscriptionsRef = useRef<Set<Subscription>>(new Set());
  const configCacheRef = useRef<DashboardConfig | null>(null);
  const prefixRef = useRef<string>('');

  const fetchSessions = useCallback(async () => {
    if (isUnmountedRef.current) return;
    try {
      const url = new URL('/api/chat', window.location.origin);
      if (activeWorkspaceId) url.searchParams.set('workspaceId', activeWorkspaceId);

      const res = await fetch(url.toString());
      if (!res.ok) {
        logger.warn(`[Realtime] Failed to fetch sessions: HTTP ${res.status}`);
        return;
      }

      const contentType = res.headers.get('content-type') ?? '';
      if (!contentType.includes('application/json')) {
        const preview = (await res.text()).slice(0, 120);
        logger.warn(`[Realtime] /api/chat returned non-JSON payload: ${preview}`);
        return;
      }

      const data = (await res.json()) as { sessions?: ConversationMeta[] };
      setTimeout(() => setSessions(data.sessions || []), 0);
    } catch (err) {
      logger.warn('[Realtime] Failed to fetch sessions', err);
    }
  }, [activeWorkspaceId]);

  const connect = useCallback(async () => {
    if (mqttClientRef.current || isUnmountedRef.current) return;

    try {
      if (!configCacheRef.current) {
        const res = await fetch('/api/config');
        if (isUnmountedRef.current) return;

        if (!res.ok) {
          logger.warn(`[Realtime] /api/config failed: HTTP ${res.status}`);
          return;
        }

        const contentType = res.headers.get('content-type') ?? '';
        if (!contentType.includes('application/json')) {
          const preview = (await res.text()).slice(0, 120);
          logger.warn(`[Realtime] /api/config returned non-JSON payload: ${preview}`);
          return;
        }

        configCacheRef.current = (await res.json()) as DashboardConfig;
      }

      const config = configCacheRef.current;
      if (!config) return;

      prefixRef.current = `${config.app}/${config.stage}/`;

      if (!config.realtime?.url || isUnmountedRef.current || mqttClientRef.current) return;

      const clientId = `dash_${Math.random().toString(36).substring(2, 10)}`;
      const token = 'dashboard-dev-token-elegant';

      // AWS IoT Core standard WebSocket URL with custom authorizer
      const host = config.realtime.url.replace(/^wss?:\/\//, '').replace(/\/mqtt$/, '');
      const authorizerName = config.realtime.authorizer;
      const mqttUrl = `wss://${host}/mqtt?x-amz-customauthorizer-name=${authorizerName}&x-amz-customauthorizer-token=${encodeURIComponent(token)}&clientId=${encodeURIComponent(clientId)}`;

      logger.info(
        `[Realtime] ⚡ Connecting to: wss://${host}/mqtt (Authorizer: ${authorizerName})`
      );

      const client = mqtt.connect(mqttUrl, {
        clientId,
        protocol: 'wss',
        protocolVersion: 4,
        clean: true,
        reconnectPeriod: 5000,
        connectTimeout: 30000,
      });

      client.on('connect', () => {
        logger.info('[Realtime] ✅ Connected');
        setIsConnected(true);
        setError(null);

        // Restore existing subscriptions
        subscriptionsRef.current.forEach((sub) => {
          const prefixed = sub.topics.map((t) => `${prefixRef.current}${t}`);
          logger.info(`[Realtime] Restoring subscription to: ${prefixed.join(', ')}`);
          client.subscribe(prefixed);
        });
      });

      client.on('reconnect', () => logger.info('[Realtime] 🔄 Reconnecting...'));
      client.on('offline', () => logger.warn('[Realtime] 🔌 Client went offline'));
      client.on('message', (topic, payload) => {
        try {
          const payloadStr = payload.toString();
          logger.info(
            `[Realtime:MQTT] Received on ${topic}: ${payloadStr.substring(0, 200)}${payloadStr.length > 200 ? '...' : ''}`
          );

          const data = JSON.parse(payloadStr) as RealtimeMessage;
          const displayTopic = topic.startsWith(prefixRef.current)
            ? topic.slice(prefixRef.current.length)
            : topic;

          let matchCount = 0;
          subscriptionsRef.current.forEach((sub) => {
            const matches = sub.topics.some((t, index) => {
              // 1. Exact match
              if (t === displayTopic) return true;

              // 2. MQTT Wildcard matching (using pre-compiled regex)
              return sub.compiledTopics[index].test(displayTopic);
            });
            if (matches) {
              matchCount++;
              sub.callback(displayTopic, data);
            }
          });

          if (matchCount === 0) {
            logger.warn(`[Realtime:MQTT] No subscription matched for topic: ${displayTopic}`);
          } else {
            logger.info(`[Realtime:MQTT] Dispatched to ${matchCount} subscribers`);
          }
        } catch (e) {
          logger.error('[Realtime:MQTT] Failed to parse message', e);
        }
      });

      client.on('error', (err) => {
        logger.error('[Realtime] 🚫 Error:', err.message);
        setError(err);
      });

      client.on('close', () => setIsConnected(false));

      mqttClientRef.current = client;
    } catch (e) {
      logger.error('[Realtime] Setup failed', e);
    }
  }, []);

  useEffect(() => {
    isUnmountedRef.current = false;
    void connect();
    const timer = setTimeout(() => {
      void fetchSessions(); // Immediate fetch on mount
    }, 0);

    const interval = setInterval(() => {
      // Periodic fetch as fallback
      if (!isUnmountedRef.current && !document.hidden) {
        void fetchSessions();
      }
    }, 60000); // Increased to 60s

    return () => {
      isUnmountedRef.current = true;
      clearTimeout(timer);
      clearInterval(interval);
      if (mqttClientRef.current) {
        mqttClientRef.current.end(true);
        mqttClientRef.current = null;
      }
    };
  }, [connect, fetchSessions]);

  // Re-fetch sessions when workspace context changes
  useEffect(() => {
    const timer = setTimeout(() => {
      void fetchSessions();
    }, 0);
    return () => clearTimeout(timer);
  }, [activeWorkspaceId, fetchSessions]);

  const subscribe = useCallback((topics: string[], callback: MessageCallback) => {
    const compiledTopics = topics.map(
      (t) =>
        new RegExp(
          `^${t
            .replace(/\//g, '\\/') // Escape slashes
            .replace(/\+/g, '[^\\/]+') // '+' matches one level
            .replace(/#/g, '.*')}$`
        )
    );
    const sub = { topics, compiledTopics, callback };
    subscriptionsRef.current.add(sub);

    if (mqttClientRef.current?.connected) {
      const prefixed = topics.map((t) => `${prefixRef.current}${t}`);
      logger.info(`[Realtime] Subscribing to NEW topics: ${prefixed.join(', ')}`);
      mqttClientRef.current.subscribe(prefixed);
    }

    return () => {
      logger.info(`[Realtime] Removing subscription for topics: ${topics.join(', ')}`);
      subscriptionsRef.current.delete(sub);
    };
  }, []);

  return (
    <RealtimeContext.Provider
      value={{
        isConnected,
        error,
        userId: STABLE_USER_ID,
        subscribe,
        sessions,
        pendingMessages,
        setPendingMessages,
        fetchSessions,
        isLive: isConnected,
      }}
    >
      {children}
    </RealtimeContext.Provider>
  );
}
