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 {
  fetchAndExtractMetadata,
  extractMetadataFromImage,
  embedMetadataInImage,
  detectImageFormat,
  getMimeType,
  getExtension,
  ImageMetadata,
  convertImageDataToPng,
  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) {
      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 banner, stored in avatar image metadata
 * The banner URL is embedded in avatar metadata and syncs via the avatar
 * @returns The current banner URL, a setter function, and loading state
 */
export function useUserBanner(): [
  string | undefined,
  (banner: string | undefined) => Promise<void>,
  boolean
] {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const [banner, setBanner] = useState<string | undefined>();
  const [loading, setLoading] = useState(true);

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

        const profile = await mx.getProfileInfo(userId);
        const avatarUrl = profile.avatar_url;
        console.log('[useUserBanner.loadBanner] Loading banner from avatar:', avatarUrl);
        
        if (!avatarUrl) {
          setBanner(undefined);
          setLoading(false);
          return;
        }

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

        // Always use current session's token to avoid stale tokens during account switches
        const accessToken = getCurrentAccessToken();
        const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
        console.log('[useUserBanner.loadBanner] Extracted banner:', metadata.banner);
        setBanner(metadata.banner);
      } catch {
        setBanner(undefined);
      }
      setLoading(false);
    };

    loadBanner();

    // Listen for avatar changes and reload banner
    const userId = mx.getUserId();
    if (!userId) return undefined;
    
    const user = mx.getUser(userId);
    const onAvatarChange: UserEventHandlerMap[UserEvent.AvatarUrl] = (newAvatarUrl) => {
      console.log('[useUserBanner] Avatar changed event fired, new URL:', newAvatarUrl);
      loadBanner();
    };

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

  const updateBanner = useCallback(async (newBanner: string | undefined) => {
    const userId = mx.getUserId();
    if (!userId) {
      throw new Error('No user ID');
    }

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

    if (!avatarUrl) {
      throw new Error('No avatar set. Please upload an avatar first.');
    }

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

    // Detect image format — banner lives in PNG tEXt, so convert other formats first
    let workingData: ArrayBuffer | Uint8Array = avatarData;
    let format = detectImageFormat(workingData);

    if (format === 'unknown') {
      throw new Error('Unsupported avatar image format');
    }

    if (format !== 'png') {
      console.log('[updateBanner] Converting', format, 'avatar to PNG for banner metadata');
      const pngData = await convertImageDataToPng(workingData);
      if (!pngData) {
        throw new Error('Failed to convert avatar to PNG for banner metadata');
      }
      workingData = pngData;
      format = 'png';
    }

    // Get existing metadata to preserve
    const existingMetadata = extractMetadataFromImage(workingData);
    console.log('[updateBanner] Existing metadata:', existingMetadata);

    // Modify image metadata with new banner, preserving color.
    // Empty string clears banner (PNG embed drops falsy fields after stripping old chunks).
    const newMetadata: ImageMetadata = {
      color: existingMetadata.color,
      banner: newBanner ?? '',
      avatarBorderColor: existingMetadata.avatarBorderColor,
      gradient: existingMetadata.gradient,
    };
    console.log('[updateBanner] New metadata to embed:', newMetadata);

    const newAvatarData = embedMetadataInImage(workingData, newMetadata);

    if (!newAvatarData) {
      throw new Error('Failed to embed banner in avatar metadata');
    }

    // Verify the banner was embedded correctly before uploading
    const verifyMetadata = extractMetadataFromImage(newAvatarData);
    console.log('[updateBanner] Verification - extracted metadata from new avatar:', verifyMetadata);

    if ((newBanner || undefined) !== verifyMetadata.banner) {
      console.error('[updateBanner] Banner verification FAILED!', 'Expected:', newBanner, 'Got:', verifyMetadata.banner);
      throw new Error('Banner verification failed');
    }
    console.log('[updateBanner] Banner verification passed');

    // 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('[updateBanner] Avatar uploaded successfully, MXC:', uploadResponse.content_uri);

    // Update profile with new avatar
    try {
      console.log('[updateBanner] Setting avatar URL to:', uploadResponse.content_uri);
      await Promise.race([
        mx.setAvatarUrl(uploadResponse.content_uri),
        new Promise((_, reject) => {
          setTimeout(() => reject(new Error('setAvatarUrl timeout')), 30000);
        })
      ]);
      console.log('[updateBanner] setAvatarUrl completed');
      
      // Manually sync user object to ensure event listeners are triggered
      const user = mx.getUser(userId);
      if (user && user.avatarUrl !== uploadResponse.content_uri) {
        console.log('[updateBanner] Manually syncing user avatar URL');
        user.setAvatarUrl(uploadResponse.content_uri);
      }
      console.log('[updateBanner] Banner update complete');
    } catch (error) {
      throw new Error(`Failed to update avatar URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }

    // Note: Don't set banner here - let the avatar change event handle it
    // setBanner(newBanner);
  }, [mx, useAuthentication]);

  return [banner, updateBanner, loading];
}

// Cache for user banners to avoid refetching for each message
const userBannerCache = new Map<string, { bannerMxc: string | undefined; timestamp: number }>();
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

/**
 * Hook to get another user's chosen profile banner from their avatar metadata
 * @param userId - The user ID to get banner for
 * @param avatarMxc - The user's avatar MXC URL
 * @returns The user's chosen banner as a blob URL or undefined
 */
export function useOtherUserBanner(userId: string, avatarMxc: string | undefined): string | undefined {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const [bannerBlobUrl, setBannerBlobUrl] = useState<string | undefined>();

  useEffect(() => {
    let blobUrl: string | undefined;

    if (!avatarMxc) {
      console.log('[useOtherUserBanner] No avatarMxc provided for', userId);
      setBannerBlobUrl(undefined);
      return undefined;
    }

    const loadBanner = async () => {
      console.log('[useOtherUserBanner] Loading banner for', userId, 'avatarMxc:', avatarMxc);
      
      // Check cache first
      const cacheKey = `${userId}:${avatarMxc}`;
      const cached = userBannerCache.get(cacheKey);
      
      let bannerMxc: string | undefined;
      
      if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
        // Use cached banner MXC
        console.log('[useOtherUserBanner] Using cached bannerMxc:', cached.bannerMxc);
        bannerMxc = cached.bannerMxc;
      } else {
        // Fetch banner MXC from avatar metadata
        const httpUrl = mxcUrlToHttp(mx, avatarMxc, useAuthentication);
        console.log('[useOtherUserBanner] Fetching metadata from:', httpUrl);
        
        if (!httpUrl) {
          console.log('[useOtherUserBanner] Failed to get HTTP URL');
          setBannerBlobUrl(undefined);
          return;
        }

        // Always use current session's token to avoid stale tokens during account switches
        const accessToken = getCurrentAccessToken();
        const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
        
        console.log('[useOtherUserBanner] Extracted metadata:', metadata);
        
        bannerMxc = metadata.banner;
        
        // Cache the banner MXC (not the blob URL)
        userBannerCache.set(cacheKey, { bannerMxc, timestamp: Date.now() });
      }
      
      if (!bannerMxc) {
        console.log('[useOtherUserBanner] No banner MXC found');
        setBannerBlobUrl(undefined);
        return;
      }

      console.log('[useOtherUserBanner] Fetching banner image from MXC:', bannerMxc);
      
      // Fetch the banner image data with authentication
      const bannerHttpUrl = mxcUrlToHttp(mx, bannerMxc, useAuthentication);
      if (!bannerHttpUrl) {
        console.log('[useOtherUserBanner] Failed to convert banner MXC to HTTP URL');
        setBannerBlobUrl(undefined);
        return;
      }

      console.log('[useOtherUserBanner] Banner HTTP URL:', bannerHttpUrl);

      // Always use current session's token to avoid stale tokens during account switches
      const accessToken = getCurrentAccessToken();
      const headers: HeadersInit = {};
      if (useAuthentication && accessToken) {
        headers.Authorization = `Bearer ${accessToken}`;
      }

      const response = await fetch(bannerHttpUrl, { headers });
      if (!response.ok) {
        setBannerBlobUrl(undefined);
        return;
      }

      const blob = await response.blob();
      blobUrl = URL.createObjectURL(blob);
      setBannerBlobUrl(blobUrl);
    };

    loadBanner();

    // Cleanup blob URL when component unmounts or deps change
    return () => {
      if (blobUrl) {
        URL.revokeObjectURL(blobUrl);
      }
    };
  }, [mx, useAuthentication, avatarMxc, userId]);

  return bannerBlobUrl;
}
