import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  buildLiveShowStreamUrl,
  DEFAULT_LIVE_SHOW_SETTINGS,
  fetchLiveShowState,
  fetchLiveShowUpdates,
  LiveShowCursor,
  LiveShowEvent,
  LiveShowPhoto,
  LiveShowSettings,
  LiveShowState,
  LiveShowError,
} from '../services/liveShowApi';

export type LiveShowStatus = 'loading' | 'ready' | 'error';
export type LiveShowConnection = 'idle' | 'sse' | 'polling';

const MAX_PHOTOS = 200;
const POLL_INTERVAL_MS = 12_000;
const POLL_HIDDEN_INTERVAL_MS = 30_000;

function mergePhotos(existing: LiveShowPhoto[], incoming: LiveShowPhoto[]): LiveShowPhoto[] {
  if (incoming.length === 0) {
    return existing;
  }

  const byId = new Map<number, LiveShowPhoto>();
  existing.forEach((photo) => byId.set(photo.id, photo));
  incoming.forEach((photo) => {
    if (!byId.has(photo.id)) {
      byId.set(photo.id, photo);
    }
  });

  return Array.from(byId.values()).slice(-MAX_PHOTOS);
}

function resolveErrorMessage(error: unknown): string {
  if (error instanceof LiveShowError) {
    return error.message || 'Live Show konnte nicht geladen werden.';
  }

  if (error instanceof Error) {
    return error.message || 'Live Show konnte nicht geladen werden.';
  }

  return 'Live Show konnte nicht geladen werden.';
}

function safeParseJson<T>(value: string): T | null {
  try {
    return JSON.parse(value) as T;
  } catch (error) {
    console.warn('Live show event payload parse failed:', error);
    return null;
  }
}

export type LiveShowStateResult = {
  status: LiveShowStatus;
  connection: LiveShowConnection;
  error: string | null;
  event: LiveShowEvent | null;
  settings: LiveShowSettings;
  settingsVersion: string;
  photos: LiveShowPhoto[];
  cursor: LiveShowCursor | null;
};

export function useLiveShowState(token: string | null, limit = 50): LiveShowStateResult {
  const [status, setStatus] = useState<LiveShowStatus>('loading');
  const [connection, setConnection] = useState<LiveShowConnection>('idle');
  const [error, setError] = useState<string | null>(null);
  const [event, setEvent] = useState<LiveShowEvent | null>(null);
  const [settings, setSettings] = useState<LiveShowSettings>(DEFAULT_LIVE_SHOW_SETTINGS);
  const [settingsVersion, setSettingsVersion] = useState('');
  const [photos, setPhotos] = useState<LiveShowPhoto[]>([]);
  const [cursor, setCursor] = useState<LiveShowCursor | null>(null);
  const [visible, setVisible] = useState(
    typeof document !== 'undefined' ? document.visibilityState === 'visible' : true
  );
  const cursorRef = useRef<LiveShowCursor | null>(null);
  const settingsVersionRef = useRef<string>('');
  const eventSourceRef = useRef<EventSource | null>(null);
  const pollingTimerRef = useRef<number | null>(null);
  const pollInFlight = useRef(false);

  const updateCursor = useCallback((next: LiveShowCursor | null) => {
    cursorRef.current = next;
    setCursor(next);
  }, []);

  const applySettings = useCallback((nextSettings: LiveShowSettings, nextVersion: string) => {
    setSettings(nextSettings);
    setSettingsVersion(nextVersion);
    settingsVersionRef.current = nextVersion;
  }, []);

  const applyPhotos = useCallback(
    (incoming: LiveShowPhoto[], nextCursor?: LiveShowCursor | null) => {
      if (incoming.length === 0) {
        return;
      }

      setPhotos((existing) => mergePhotos(existing, incoming));
      if (nextCursor) {
        updateCursor(nextCursor);
      }
    },
    [updateCursor]
  );

  const closeEventSource = useCallback(() => {
    if (eventSourceRef.current) {
      eventSourceRef.current.close();
      eventSourceRef.current = null;
    }
  }, []);

  const clearPolling = useCallback(() => {
    if (pollingTimerRef.current !== null) {
      window.clearInterval(pollingTimerRef.current);
      pollingTimerRef.current = null;
    }
  }, []);

  const pollUpdates = useCallback(async () => {
    if (!token || pollInFlight.current) {
      return;
    }

    pollInFlight.current = true;
    try {
      const update = await fetchLiveShowUpdates(token, {
        cursor: cursorRef.current,
        settingsVersion: settingsVersionRef.current,
        limit,
      });

      if (update.settings) {
        applySettings(update.settings, update.settings_version);
      } else if (update.settings_version && update.settings_version !== settingsVersionRef.current) {
        settingsVersionRef.current = update.settings_version;
        setSettingsVersion(update.settings_version);
      }

      if (update.photos.length > 0) {
        applyPhotos(update.photos, update.cursor ?? cursorRef.current);
      } else if (update.cursor) {
        updateCursor(update.cursor);
      }
    } catch (err) {
      console.warn('Live show polling error:', err);
    } finally {
      pollInFlight.current = false;
    }
  }, [applyPhotos, applySettings, limit, token, updateCursor]);

  const startPolling = useCallback(() => {
    clearPolling();
    setConnection('polling');
    void pollUpdates();
    const interval = visible ? POLL_INTERVAL_MS : POLL_HIDDEN_INTERVAL_MS;
    pollingTimerRef.current = window.setInterval(() => {
      void pollUpdates();
    }, interval);
  }, [clearPolling, pollUpdates, visible]);

  const startSse = useCallback(() => {
    if (!token || typeof EventSource === 'undefined') {
      return false;
    }

    closeEventSource();

    const url = buildLiveShowStreamUrl(token, {
      cursor: cursorRef.current,
      settingsVersion: settingsVersionRef.current,
      limit,
    });

    try {
      const stream = new EventSource(url);
      eventSourceRef.current = stream;
      setConnection('sse');

      stream.addEventListener('settings.updated', (event) => {
        const payload = safeParseJson<{
          settings?: LiveShowSettings;
          settings_version?: string;
        }>((event as MessageEvent<string>).data);
        if (!payload) {
          return;
        }
        if (payload.settings && payload.settings_version) {
          applySettings(payload.settings, payload.settings_version);
        }
      });

      stream.addEventListener('photo.approved', (event) => {
        const payload = safeParseJson<{
          photo?: LiveShowPhoto;
          cursor?: LiveShowCursor | null;
        }>((event as MessageEvent<string>).data);
        if (!payload) {
          return;
        }
        if (payload.photo) {
          applyPhotos([payload.photo], payload.cursor ?? null);
        } else if (payload.cursor) {
          updateCursor(payload.cursor);
        }
      });

      stream.addEventListener('error', () => {
        closeEventSource();
        startPolling();
      });

      return true;
    } catch (err) {
      console.warn('Live show SSE failed:', err);
      closeEventSource();
      return false;
    }
  }, [applyPhotos, applySettings, closeEventSource, limit, startPolling, token, updateCursor]);

  const startStreaming = useCallback(() => {
    clearPolling();

    if (!startSse()) {
      startPolling();
    }
  }, [clearPolling, startPolling, startSse]);

  useEffect(() => {
    const onVisibility = () => setVisible(document.visibilityState === 'visible');
    document.addEventListener('visibilitychange', onVisibility);

    return () => document.removeEventListener('visibilitychange', onVisibility);
  }, []);

  useEffect(() => {
    if (connection !== 'polling') {
      return;
    }

    startPolling();
  }, [connection, startPolling, visible]);

  useEffect(() => {
    if (!token) {
      setStatus('error');
      setError('Live Show konnte nicht geladen werden.');
      setEvent(null);
      setPhotos([]);
      updateCursor(null);
      setSettings(DEFAULT_LIVE_SHOW_SETTINGS);
      setSettingsVersion('');
      setConnection('idle');
      return;
    }

    let cancelled = false;

    const load = async () => {
      setStatus('loading');
      setError(null);
      setConnection('idle');

      try {
        const data: LiveShowState = await fetchLiveShowState(token, limit);
        if (cancelled) {
          return;
        }

        setEvent(data.event);
        setPhotos(data.photos);
        updateCursor(data.cursor);
        applySettings(data.settings, data.settings_version);
        setStatus('ready');
        startStreaming();
      } catch (err) {
        if (cancelled) {
          return;
        }

        setStatus('error');
        setError(resolveErrorMessage(err));
        setConnection('idle');
      }
    };

    load();

    return () => {
      cancelled = true;
      closeEventSource();
      clearPolling();
      setConnection('idle');
    };
  }, [applySettings, clearPolling, closeEventSource, limit, startStreaming, token, updateCursor]);

  return useMemo(
    () => ({
      status,
      connection,
      error,
      event,
      settings,
      settingsVersion,
      photos,
      cursor,
    }),
    [status, connection, error, event, settings, settingsVersion, photos, cursor]
  );
}
