import { createContext, useCallback, useEffect, useMemo, useState, PropsWithChildren } from 'react';
import { useLingui } from '@lingui/react/macro';
import { SpotifyAuth } from '@/lib/spotify/auth';
import { useAuth } from './auth-context';
import { useConfig } from '@/hooks/use-config';
import type {
  SpotifyContextType,
  SpotifyAuthState,
  SpotifyDevice,
  SpotifyPlaybackState,
  SpotifyPlaybackSnapshot,
  SpotifyTrack,
} from '@/lib/spotify/types';

const defaultSpotifyState: SpotifyAuthState = {
  isAuthenticated: false,
  accessToken: null,
  refreshToken: null,
  expiresAt: null,
  scopes: [],
};

export const SpotifyContext = createContext<SpotifyContextType | null>(null);

const deepEqual = (a: unknown, b: unknown): boolean => {
  if (a === b) return true;
  if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') {
    return false;
  }
  if (Array.isArray(a) !== Array.isArray(b)) return false;
  const aKeys = Object.keys(a as Record<string, unknown>);
  const bKeys = Object.keys(b as Record<string, unknown>);
  if (aKeys.length !== bKeys.length) return false;
  return aKeys.every((key) =>
    deepEqual(
      (a as Record<string, unknown>)[key],
      (b as Record<string, unknown>)[key]
    )
  );
};

export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
  const { roles } = useAuth();
  const isAdmin = roles?.includes('Admin') || false;
  const config = useConfig();
  const { t } = useLingui();

  // Defined inside the provider so error messages can be localized via `t`.
  const makeSpotifyRequest = async (endpoint: string, options: RequestInit = {}) => {
    const response = await fetch(`/api/spotify/${endpoint}`, {
      ...options,
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });

    if (!response.ok) {
      let errorMessage = t`Request failed`;
      try {
        const errorData = await response.json();
        errorMessage = errorData.error || errorMessage;
      } catch {
        errorMessage = t`HTTP ${response.status}: ${response.statusText}`;
      }
      throw new Error(errorMessage);
    }

    if (response.status === 204 || response.headers.get('content-length') === '0') {
      return {};
    }

    const contentType = response.headers.get('content-type') || '';
    if (!contentType.includes('application/json')) {
      console.warn(`Non-JSON response from ${endpoint}:`, contentType);
      return {};
    }

    try {
      return await response.json();
    } catch (error) {
      console.warn(`Failed to parse JSON response from ${endpoint}:`, error);
      return {};
    }
  };

  const [authState, setAuthState] = useState<SpotifyAuthState>(defaultSpotifyState);

  const [currentTrack, setCurrentTrack] = useState<SpotifyTrack | null>(null);
  const [playbackState, setPlaybackState] = useState<SpotifyPlaybackState | null>(null);

  const [devices, setDevices] = useState<SpotifyDevice[]>([]);
  const [activeDevice, setActiveDeviceState] = useState<SpotifyDevice | null>(null);

  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const [capturedState, setCapturedState] = useState<SpotifyPlaybackSnapshot | null>(null);
  const [isCaptureLoading, setIsCaptureLoading] = useState(false);
  const [isResumeLoading, setIsResumeLoading] = useState(false);

  const spotifyAuth = useMemo(
    () => new SpotifyAuth(config.spotifyClientId, config.spotifyRedirectUri),
    [config.spotifyClientId, config.spotifyRedirectUri]
  );

  useEffect(() => {
    if (isAdmin) {
      checkExistingAuth();
      handleOAuthCallback();
    }
  }, [isAdmin]);

  const checkExistingAuth = async () => {
    try {
      const response = await fetch('/api/spotify/token', {
        credentials: 'include',
      });
      
      if (response.ok) {
        const data = await response.json();
        if (data.access_token) {
          setAuthState({
            isAuthenticated: true,
            accessToken: data.access_token,
            refreshToken: data.refresh_token,
            expiresAt: Date.now() + (3600 * 1000),
            scopes: [],
          });
          
          await Promise.all([getDevices(), refreshPlaybackState()]);
        }
      }
    } catch (error) {
      console.error('Failed to check existing auth:', error);
    }
  };

  const handleOAuthCallback = () => {
    const urlParams = new URLSearchParams(window.location.search);
    const spotifyAuth = urlParams.get('spotify_auth');
    const error = urlParams.get('spotify_error');
    const details = urlParams.get('details');

    if (spotifyAuth === 'success') {
      checkExistingAuth();
      
      const newUrl = new URL(window.location.href);
      newUrl.searchParams.delete('spotify_auth');
      newUrl.searchParams.delete('state');
      window.history.replaceState({}, '', newUrl.toString());
    }

    if (error) {
      const errorMessage = details
        ? t`Authentication failed: ${error} - ${decodeURIComponent(details)}`
        : t`Authentication failed: ${error}`;
      setError(errorMessage);
      
      console.error('Spotify OAuth Error:', { error, details });
      
      const newUrl = new URL(window.location.href);
      newUrl.searchParams.delete('spotify_error');
      newUrl.searchParams.delete('details');
      window.history.replaceState({}, '', newUrl.toString());
    }
  };

  const login = useCallback(() => {
    if (!isAdmin) return;
    spotifyAuth.startAuthFlow(window.location.pathname);
  }, [isAdmin, spotifyAuth]);

  const logout = useCallback(() => {
    setAuthState(defaultSpotifyState);
    setCurrentTrack(null);
    setPlaybackState(null);
    setDevices([]);
    setActiveDeviceState(null);
    setError(null);
    
    document.cookie = 'spotify_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    document.cookie = 'spotify_refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
  }, []);

  const play = useCallback(async (deviceId?: string) => {
    if (!authState.isAuthenticated) return;

    setIsLoading(true);
    setError(null);

    try {
      await makeSpotifyRequest('playback', {
        method: 'POST',
        body: JSON.stringify({ action: 'play', deviceId }),
      });

      setTimeout(refreshPlaybackState, 500);
    } catch (error) {
      if (error instanceof Error && !error.message.includes('JSON')) {
        setError(error.message);
      }
      console.warn('Playback action completed with warning:', error);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const playTrack = useCallback(async (trackId: string, deviceId?: string, positionMs?: number) => {
    if (!authState.isAuthenticated) return;

    setIsLoading(true);
    setError(null);

    try {
      await makeSpotifyRequest('playback', {
        method: 'POST',
        body: JSON.stringify({ action: 'playTrack', trackId, deviceId, positionMs }),
      });

      setTimeout(refreshPlaybackState, 500);
    } catch (error) {
      if (error instanceof Error && !error.message.includes('JSON')) {
        setError(error.message);
      }
      console.warn('Track playback action completed with warning:', error);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const pause = useCallback(async () => {
    if (!authState.isAuthenticated) return;
    
    setIsLoading(true);
    setError(null);
    
    try {
      await makeSpotifyRequest('playback', {
        method: 'POST',
        body: JSON.stringify({ action: 'pause' }),
      });
      
      setTimeout(refreshPlaybackState, 500);
    } catch (error) {
      if (error instanceof Error && !error.message.includes('JSON')) {
        setError(error.message);
      }
      console.warn('Playback action completed with warning:', error);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const skipNext = useCallback(async () => {
    if (!authState.isAuthenticated) return;
    
    setIsLoading(true);
    setError(null);
    
    try {
      await makeSpotifyRequest('playback', {
        method: 'POST',
        body: JSON.stringify({ action: 'next' }),
      });
      
      setTimeout(refreshPlaybackState, 500);
    } catch (error) {
      if (error instanceof Error && !error.message.includes('JSON')) {
        setError(error.message);
      }
      console.warn('Playback action completed with warning:', error);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const skipPrevious = useCallback(async () => {
    if (!authState.isAuthenticated) return;
    
    setIsLoading(true);
    setError(null);
    
    try {
      await makeSpotifyRequest('playback', {
        method: 'POST',
        body: JSON.stringify({ action: 'previous' }),
      });
      
      setTimeout(refreshPlaybackState, 500);
    } catch (error) {
      if (error instanceof Error && !error.message.includes('JSON')) {
        setError(error.message);
      }
      console.warn('Playback action completed with warning:', error);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const setVolume = useCallback(async (volumePercent: number) => {
    if (!authState.isAuthenticated) return;
    
    setIsLoading(true);
    setError(null);
    
    try {
      await makeSpotifyRequest('playback', {
        method: 'POST',
        body: JSON.stringify({ action: 'volume', volumePercent }),
      });
    } catch (error) {
      setError(error instanceof Error ? error.message : t`Failed to set volume`);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const getDevices = useCallback(async () => {
    if (!authState.isAuthenticated) return;
    
    setIsLoading(true);
    setError(null);
    
    try {
      const data = await makeSpotifyRequest('playback?type=devices');
      setDevices(data.devices || []);
      
      const active = data.devices?.find((d: SpotifyDevice) => d.is_active);
      if (active) {
        setActiveDeviceState(active);
      }
    } catch (error) {
      setError(error instanceof Error ? error.message : t`Failed to get devices`);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const setActiveDevice = useCallback(async (deviceId: string) => {
    if (!authState.isAuthenticated) return;
    
    setIsLoading(true);
    setError(null);
    
    try {
      await makeSpotifyRequest('playback', {
        method: 'POST',
        body: JSON.stringify({ action: 'transfer', deviceId }),
      });
      
      const device = devices.find(d => d.id === deviceId);
      if (device) {
        setActiveDeviceState(device);
      }
      
      setTimeout(getDevices, 1000);
    } catch (error) {
      setError(error instanceof Error ? error.message : t`Failed to set active device`);
    } finally {
      setIsLoading(false);
    }
  }, [authState.isAuthenticated, devices, t]);

  const refreshPlaybackState = useCallback(async () => {
    if (!authState.isAuthenticated) return;
    
    try {
      const data = await makeSpotifyRequest('playback?type=state');
      const state = data.playbackState;

      setPlaybackState((prev) => (deepEqual(prev, state ?? null) ? prev : state));
      setCurrentTrack((prev) => {
        const next = state?.item || null;
        return deepEqual(prev, next) ? prev : next;
      });

      if (state?.device) {
        setActiveDeviceState((prev) =>
          deepEqual(prev, state.device) ? prev : state.device
        );
      }
    } catch (error) {
      console.warn('Failed to refresh playback state:', error);
    }
  }, [authState.isAuthenticated, t]);

  useEffect(() => {
    if (!authState.isAuthenticated) return;

    const poll = () => {
      if (document.hidden) return;
      refreshPlaybackState();
    };

    const handleVisibilityChange = () => {
      if (!document.hidden) {
        refreshPlaybackState();
      }
    };

    const interval = setInterval(poll, 5000);
    document.addEventListener('visibilitychange', handleVisibilityChange);

    return () => {
      clearInterval(interval);
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, [authState.isAuthenticated, refreshPlaybackState]);

  const capturePlaybackState = useCallback(async () => {
    if (!authState.isAuthenticated) return;
    
    setIsCaptureLoading(true);
    setError(null);
    
    try {
      const response = await makeSpotifyRequest('capture', {
        method: 'POST',
      });
      
      if (response.snapshot) {
        setCapturedState(response.snapshot);
      }
    } catch (error) {
      setError(error instanceof Error ? error.message : t`Failed to capture playback state`);
    } finally {
      setIsCaptureLoading(false);
    }
  }, [authState.isAuthenticated, t]);

  const resumePlaybackState = useCallback(async () => {
    if (!authState.isAuthenticated || !capturedState) return;
    
    setIsResumeLoading(true);
    setError(null);
    
    try {
      await makeSpotifyRequest('resume', {
        method: 'POST',
        body: JSON.stringify({ snapshot: capturedState }),
      });
      
      setTimeout(refreshPlaybackState, 1000);
    } catch (error) {
      setError(error instanceof Error ? error.message : t`Failed to resume playback state`);
    } finally {
      setIsResumeLoading(false);
    }
  }, [authState.isAuthenticated, capturedState, refreshPlaybackState, t]);

  const clearCapturedState = useCallback(() => {
    setCapturedState(null);
  }, []);

  const searchTracks = useCallback(async (query: string): Promise<SpotifyTrack[]> => {
    if (!query.trim()) return [];

    try {
      const response = await fetch(`/api/spotify/search?q=${encodeURIComponent(query)}`);

      if (!response.ok) {
        throw new Error('Search failed');
      }

      const data = await response.json();
      return data.tracks || [];
    } catch (error) {
      console.error('Failed to search tracks:', error);
      return [];
    }
  }, []);

  const contextValue: SpotifyContextType = useMemo(() => ({
    ...authState,
    currentTrack,
    playbackState,
    devices,
    activeDevice,
    isLoading,
    error,
    // Capture/Resume state
    capturedState,
    isCaptureLoading,
    isResumeLoading,
    login,
    logout,
    play,
    playTrack,
    pause,
    skipNext,
    skipPrevious,
    setVolume,
    getDevices,
    setActiveDevice,
    refreshPlaybackState,
    // Capture/Resume methods
    capturePlaybackState,
    resumePlaybackState,
    clearCapturedState,
    // Search
    searchTracks,
  }), [
    authState,
    currentTrack,
    playbackState,
    devices,
    activeDevice,
    isLoading,
    error,
    capturedState,
    isCaptureLoading,
    isResumeLoading,
    login,
    logout,
    play,
    playTrack,
    pause,
    skipNext,
    skipPrevious,
    setVolume,
    getDevices,
    setActiveDevice,
    refreshPlaybackState,
    capturePlaybackState,
    resumePlaybackState,
    clearCapturedState,
    searchTracks,
  ]);

  if (!isAdmin) {
    return <>{children}</>;
  }

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