import { useCallback, useEffect, useState } from 'react';
import { MatrixClient, UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
import { useMatrixClient } from './useMatrixClient';
import { useMediaAuthentication } from './useMediaAuthentication';
import { mxcUrlToHttp } from '../utils/matrix';
import { getCurrentAccessToken } from '../utils/auth';
import {
  embedColorInImage,
  extractColorFromImage,
  removeColorFromImage,
  fetchAndExtractColor,
  detectImageFormat,
  getMimeType,
  getExtension,
  uint8ArrayToBlob,
} from '../utils/imageMetadata';

/**
 * Fetches the user's current avatar as raw image data
 */
async function fetchAvatarData(
  mx: MatrixClient,
  avatarMxc: string,
  useAuthentication: boolean
): Promise<ArrayBuffer | null> {
  const url = mxcUrlToHttp(mx, avatarMxc, useAuthentication);
  if (!url) return null;

  try {
    // Always use current session's token to avoid stale tokens during account switches
    const accessToken = getCurrentAccessToken();
    let response = await fetch(url, {
      method: 'GET',
      headers: accessToken && useAuthentication ? { Authorization: `Bearer ${accessToken}` } : undefined,
    });
    
    // If we got a 401 and we tried with auth, fallback to unauthenticated request
    if (!response.ok && response.status === 401 && accessToken && useAuthentication) {
      console.warn('[fetchAvatarData] Auth failed (401), attempting unauthenticated fallback');
      response = await fetch(url, { method: 'GET' });
    }
    
    if (!response.ok) return null;
    return await response.arrayBuffer();
  } catch {
    return null;
  }
}

/**
 * Hook to manage the user's chosen profile color, stored in avatar image metadata
 * The color is embedded in PNG tEXt chunk or WebP XMP chunk and syncs via the avatar
 * @returns The current color, a setter function, and loading state
 */
export function useUserColor(): [
  string | undefined,
  (color: string | undefined) => Promise<void>,
  boolean
] {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const [color, setColor] = useState<string | undefined>();
  const [loading, setLoading] = useState(true);

  // Extract color from current avatar on mount and when profile changes
  useEffect(() => {
    const loadColor = async () => {
      setLoading(true);
      try {
        const userId = mx.getUserId();
        if (!userId) {
          setLoading(false);
          return;
        }

        const profile = await mx.getProfileInfo(userId);
        const avatarUrl = profile.avatar_url;
        
        if (!avatarUrl) {
          setColor(undefined);
          setLoading(false);
          return;
        }

        const httpUrl = mxcUrlToHttp(mx, avatarUrl, useAuthentication);
        if (!httpUrl) {
          setColor(undefined);
          setLoading(false);
          return;
        }

        // Always use current session's token to avoid stale tokens during account switches
        const accessToken = getCurrentAccessToken();
        const extractedColor = await fetchAndExtractColor(httpUrl, useAuthentication ? accessToken : null);
        setColor(extractedColor);
      } catch (e) {
        console.error('Failed to load user color from avatar:', e);
        setColor(undefined);
      }
      setLoading(false);
    };

    loadColor();

    // Listen for avatar changes and reload color
    const userId = mx.getUserId();
    if (!userId) return undefined;
    
    const user = mx.getUser(userId);
    const onAvatarChange: UserEventHandlerMap[UserEvent.AvatarUrl] = () => {
      loadColor();
    };

    user?.on(UserEvent.AvatarUrl, onAvatarChange);
    
    return () => {
      user?.removeListener(UserEvent.AvatarUrl, onAvatarChange);
    };
  }, [mx, useAuthentication]);

  const updateColor = useCallback(async (newColor: string | undefined) => {
    const userId = mx.getUserId();
    if (!userId) return;

    try {
      const profile = await mx.getProfileInfo(userId);
      const avatarUrl = profile.avatar_url;

      if (!avatarUrl) {
        // No avatar to embed color in
        console.warn('Cannot set color: no avatar image set');
        throw new Error('No avatar set');
      }

      // Fetch current avatar
      const avatarData = await fetchAvatarData(mx, avatarUrl, useAuthentication);
      if (!avatarData) {
        console.error('Failed to fetch current avatar');
        throw new Error('Failed to fetch avatar');
      }

      console.log('Original avatar size:', avatarData.byteLength, 'bytes');

      // Detect image format
      const format = detectImageFormat(avatarData);
      console.log('Detected image format:', format);
      
      if (format === 'unknown') {
        console.error('Unsupported avatar image format - must be PNG, WebP, JPEG, or GIF');
        throw new Error('Unsupported image format');
      }

      // Modify image metadata
      let newAvatarData: Uint8Array | null;
      if (newColor) {
        newAvatarData = embedColorInImage(avatarData, newColor);
      } else {
        newAvatarData = removeColorFromImage(avatarData);
      }

      if (!newAvatarData) {
        console.error('Failed to modify avatar image metadata');
        throw new Error('Failed to modify image metadata');
      }

      console.log('Modified avatar size:', newAvatarData.byteLength, 'bytes');

      // Verify the color was embedded correctly before uploading
      const verifyColor = extractColorFromImage(newAvatarData);
      console.log('Verification - embedded color:', verifyColor);
      
      if (newColor && verifyColor !== newColor) {
        console.error('Color verification failed! Expected:', newColor, 'Got:', verifyColor);
        throw new Error('Color embedding verification failed');
      }

      // Upload modified avatar with correct MIME type
      const mimeType = getMimeType(format);
      const extension = getExtension(format);
      const blob = uint8ArrayToBlob(newAvatarData, mimeType);
      const uploadResponse = await mx.uploadContent(blob, {
        name: `avatar.${extension}`,
        type: mimeType,
      });

      console.log('Uploaded new avatar:', uploadResponse.content_uri);

      // Update profile with new avatar
      await mx.setAvatarUrl(uploadResponse.content_uri);

      // Manually sync user object to ensure event listeners are triggered
      const user = mx.getUser(userId);
      if (user && user.avatarUrl !== uploadResponse.content_uri) {
        user.setAvatarUrl(uploadResponse.content_uri);
      }

      setColor(newColor);
      console.log('User color updated in avatar:', newColor);
    } catch (e) {
      console.error('Failed to update user color in avatar:', e);
      throw e;
    }
  }, [mx, useAuthentication]);

  return [color, updateColor, loading];
}

/**
 * LRU Cache implementation with max size limit
 */
class LRUCache<K, V> {
  private cache: Map<K, V>;

  private maxSize: number;

  constructor(maxSize: number) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  get(key: K): V | undefined {
    const value = this.cache.get(key);
    if (value !== undefined) {
      // Move to end (most recently used)
      this.cache.delete(key);
      this.cache.set(key, value);
    }
    return value;
  }

  set(key: K, value: V): void {
    // If key exists, delete it first to re-add at end
    if (this.cache.has(key)) {
      this.cache.delete(key);
    }
    // If at max size, remove oldest (first) entry
    else if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      if (firstKey !== undefined) {
        this.cache.delete(firstKey);
      }
    }
    this.cache.set(key, value);
  }

  clear(): void {
    this.cache.clear();
  }
}

// LRU cache for user colors to avoid refetching for each message
const userColorCache = new LRUCache<string, { color: string | undefined; timestamp: number }>(100);
const CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes

/**
 * Hook to get another user's chosen profile color from their avatar metadata
 * @param userId - The user ID to get color for
 * @param avatarMxc - The user's avatar MXC URL
 * @returns The user's chosen color or undefined
 */
export function useOtherUserColor(userId: string, avatarMxc: string | undefined): string | undefined {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  
  // Check cache synchronously BEFORE initializing state to avoid flash
  const cacheKey = avatarMxc ? `${userId}:${avatarMxc}` : '';
  const cachedEntry = cacheKey ? userColorCache.get(cacheKey) : undefined;
  const cachedColor = cachedEntry && Date.now() - cachedEntry.timestamp < CACHE_TTL_MS 
    ? cachedEntry.color 
    : undefined;
  
  const [color, setColor] = useState<string | undefined>(cachedColor);

  useEffect(() => {
    if (!avatarMxc) {
      console.log('[useOtherUserColor] No avatarMxc provided for', userId);
      setColor(undefined);
      return;
    }

    // Check cache first
    const cacheKey = `${userId}:${avatarMxc}`;
    const cached = userColorCache.get(cacheKey);
    if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
      console.log('[useOtherUserColor] Using cached color for', userId, ':', cached.color);
      setColor(cached.color);
      return;
    }

    const loadColor = async () => {
      const httpUrl = mxcUrlToHttp(mx, avatarMxc, useAuthentication);
      console.log('[useOtherUserColor] Loading color for', userId, 'from', httpUrl);
      if (!httpUrl) {
        console.log('[useOtherUserColor] Failed to get HTTP URL for', avatarMxc);
        setColor(undefined);
        return;
      }

      // Always use current session's token to avoid stale tokens during account switches
      const accessToken = getCurrentAccessToken();
      const extractedColor = await fetchAndExtractColor(httpUrl, useAuthentication ? accessToken : null);
      
      console.log('[useOtherUserColor] Extracted color for', userId, ':', extractedColor);
      
      // Cache the result
      userColorCache.set(cacheKey, { color: extractedColor, timestamp: Date.now() });
      
      setColor(extractedColor);
    };

    loadColor();
  }, [mx, useAuthentication, avatarMxc, userId]);

  return color;
}

