import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from 'matrix-js-sdk';

import { cryptoCallbacks } from './secretStorageKeys';
import { clearNavToActivePathStore } from '../app/state/navToActivePath';
import { Session, getSessionStoreName } from '../app/state/sessions';

// Track if we're currently handling a key conflict to prevent loops
let handlingKeyConflict = false;

const deleteIndexedDB = (dbName: string): Promise<void> => {
  return new Promise((resolve, reject) => {
    const request = global.indexedDB.deleteDatabase(dbName);
    request.onsuccess = () => {
      console.log(`[initMatrix] Deleted IndexedDB database: ${dbName}`);
      resolve();
    };
    request.onerror = () => {
      console.error(`[initMatrix] Failed to delete IndexedDB database: ${dbName}`, request.error);
      reject(request.error);
    };
    request.onblocked = () => {
      console.warn(`[initMatrix] Delete blocked for IndexedDB database: ${dbName}`);
      // Resolve anyway as the delete might succeed later
      resolve();
    };
  });
};

const getAllIndexedDBNames = async (): Promise<string[]> => {
  try {
    if ('databases' in global.indexedDB) {
      // Add timeout to prevent hanging
      const timeoutPromise = new Promise<string[]>((resolve) => {
        setTimeout(() => {
          console.warn('[initMatrix] indexedDB.databases() timed out after 2s');
          resolve([]);
        }, 2000);
      });
      
      const databasesPromise = (global.indexedDB as any).databases().then((dbs: any[]) => {
        return dbs.map((db: any) => db.name).filter(Boolean);
      });
      
      return await Promise.race([databasesPromise, timeoutPromise]);
    }
  } catch (error) {
    console.warn('[initMatrix] Error getting IndexedDB names:', error);
  }
  // Fallback for browsers that don't support indexedDB.databases()
  return [];
};

const deleteAllMatrixDatabases = async (): Promise<void> => {
  console.log('[initMatrix] Clearing all Matrix-related IndexedDB databases...');
  
  const dbNames = await getAllIndexedDBNames();
  console.log('[initMatrix] Found databases:', dbNames);
  
  // Delete all Matrix-related databases (with matrix-js-sdk: prefix)
  const matrixDbNames = dbNames.filter(name => 
    name.startsWith('matrix-js-sdk:') || 
    name.startsWith('crypto@') || 
    name.startsWith('sync@') || 
    name === 'crypto-store' || 
    name === 'web-sync-store'
  );
  
  console.log('[initMatrix] Deleting Matrix databases:', matrixDbNames);
  
  for (const dbName of matrixDbNames) {
    try {
      await deleteIndexedDB(dbName);
    } catch (error) {
      console.error(`[initMatrix] Failed to delete ${dbName}:`, error);
    }
  }
};

/**
 * Delete the Rust crypto database to fix one-time key conflicts.
 * This happens when the local crypto state has keys that conflict with server state.
 */
const deleteRustCryptoDatabase = async (session: Session): Promise<boolean> => {
  console.log('[initMatrix] 🔑 Attempting to delete Rust crypto database to fix key conflicts...');
  
  const dbNames = await getAllIndexedDBNames();
  const storagePrefix = `${session.userId}@${session.deviceId}`;
  
  // Find crypto databases for this session
  const cryptoDbNames = dbNames.filter(name => 
    name.includes('matrix-sdk-crypto') ||
    name.includes(storagePrefix)
  );
  
  if (cryptoDbNames.length === 0) {
    console.log('[initMatrix] No crypto databases found to delete');
    return false;
  }
  
  console.log('[initMatrix] 🗑️ Deleting crypto databases:', cryptoDbNames);
  
  for (const dbName of cryptoDbNames) {
    try {
      await deleteIndexedDB(dbName);
      console.log(`[initMatrix] ✅ Deleted: ${dbName}`);
    } catch (error) {
      console.error(`[initMatrix] ❌ Failed to delete ${dbName}:`, error);
    }
  }
  
  return true;
};

/**
 * Setup error listener to detect one-time key conflicts and auto-fix them.
 */
const setupKeyConflictHandler = (mx: MatrixClient, session: Session): void => {
  // Listen for HTTP errors from the client
  let keyConflictCount = 0;
  const KEY_CONFLICT_THRESHOLD = 3;
  
  // Patch console.log/error to detect the key conflict errors
  const originalConsoleLog = console.log;
  const keyConflictPattern = /One time key.*already exists/;
  
  // We need to use a different approach - listen to the crypto module
  // Since we can't directly intercept, we'll use a MutationObserver-style approach
  // by checking for repeated key upload failures
  
  const checkKeyConflict = (args: any[]): boolean => {
    const message = args.map(a => String(a)).join(' ');
    return keyConflictPattern.test(message);
  };
  
  // Override console methods temporarily to detect the errors
  const originalError = console.error;
  const originalWarn = console.warn;
  
  const errorHandler = (...args: any[]) => {
    if (checkKeyConflict(args)) {
      keyConflictCount++;
      console.log(`[initMatrix] ⚠️ Detected key conflict (${keyConflictCount}/${KEY_CONFLICT_THRESHOLD})`);
      
      if (keyConflictCount >= KEY_CONFLICT_THRESHOLD && !handlingKeyConflict) {
        handlingKeyConflict = true;
        console.log('[initMatrix] 🔄 Too many key conflicts detected, attempting auto-fix...');
        
        // Schedule the fix for next page load to avoid issues during active session
        localStorage.setItem('cinny_fix_key_conflict', JSON.stringify({
          userId: session.userId,
          deviceId: session.deviceId,
          timestamp: Date.now()
        }));
        
        // Restore console methods
        console.error = originalError;
        console.warn = originalWarn;
        
        // Notify user and reload
        setTimeout(() => {
          if (confirm('Detected encryption key conflict. The app needs to reset your encryption session. You may need to re-verify your devices after this. Continue?')) {
            deleteRustCryptoDatabase(session).then(() => {
              window.location.reload();
            });
          } else {
            handlingKeyConflict = false;
          }
        }, 100);
      }
    }
    originalError.apply(console, args);
  };
  
  console.error = errorHandler;
  console.warn = (...args: any[]) => {
    if (checkKeyConflict(args)) {
      keyConflictCount++;
    }
    originalWarn.apply(console, args);
  };
};

export const initClient = async (session: Session): Promise<MatrixClient> => {
  const storeName = getSessionStoreName(session);
  
  // Check if we need to fix a key conflict from a previous session
  const pendingFix = localStorage.getItem('cinny_fix_key_conflict');
  if (pendingFix) {
    try {
      const fixData = JSON.parse(pendingFix);
      if (fixData.userId === session.userId && fixData.deviceId === session.deviceId) {
        console.log('[initMatrix] 🔧 Found pending key conflict fix, clearing crypto database...');
        await deleteRustCryptoDatabase(session);
        localStorage.removeItem('cinny_fix_key_conflict');
        console.log('[initMatrix] ✅ Crypto database cleared');
      }
    } catch (e) {
      localStorage.removeItem('cinny_fix_key_conflict');
    }
  }
  
  console.log('[initMatrix] ========================================');
  console.log('[initMatrix] Initializing Matrix client for session:');
  console.log('[initMatrix] User ID:', session.userId);
  console.log('[initMatrix] Device ID:', session.deviceId);
  console.log('[initMatrix] Using fallback stores:', session.fallbackSdkStores || false);
  console.log('[initMatrix] Sync store name:', storeName.sync);
  console.log('[initMatrix] Crypto store name:', storeName.crypto);
  console.log('[initMatrix] ========================================');
  
  console.log('[initMatrix] Creating Matrix client...');
  
  try {
    const indexedDBStore = new IndexedDBStore({
      indexedDB: global.indexedDB,
      localStorage: global.localStorage,
      dbName: storeName.sync,
    });

    const legacyCryptoStore = new IndexedDBCryptoStore(global.indexedDB, storeName.crypto);

    const mx = createClient({
      baseUrl: session.baseUrl,
      accessToken: session.accessToken,
      userId: session.userId,
      store: indexedDBStore,
      cryptoStore: legacyCryptoStore,
      deviceId: session.deviceId,
      timelineSupport: true,
      cryptoCallbacks: cryptoCallbacks as any,
      verificationMethods: ['m.sas.v1'],
    });

    await indexedDBStore.startup();
    
    // Initialize Rust crypto with a unique store prefix for this account
    // This ensures each account has its own isolated crypto database
    console.log('[initMatrix] Initializing Rust crypto with prefix:', `${session.userId}@${session.deviceId}`);
    await mx.initRustCrypto({
      storagePrefix: `${session.userId}@${session.deviceId}`,
    });
    
    // List databases after init to verify isolation
    try {
      const dbsAfterInit = await getAllIndexedDBNames();
      console.log('[initMatrix] 📦 Databases after initialization:', dbsAfterInit);
      
      // Check if a shared crypto database was created despite storagePrefix
      const hasSharedCrypto = dbsAfterInit.includes('matrix-js-sdk::matrix-sdk-crypto');
      if (hasSharedCrypto) {
        console.warn('[initMatrix] ⚠️  WARNING: Shared crypto database still exists after init!');
        console.warn('[initMatrix] The storagePrefix option may not be working correctly.');
      }
      
      // List account-specific databases
      const accountDbs = dbsAfterInit.filter(db => 
        db.includes(session.userId) || db.includes(session.deviceId)
      );
      console.log('[initMatrix] 📁 Account-specific databases:', accountDbs);
    } catch (e) {
      console.log('[initMatrix] Could not list databases after init:', e);
    }
    
    console.log('[initMatrix] ✅ Client initialized successfully!');

    mx.setMaxListeners(50);
    
    // Increase max listeners for User objects to prevent warnings when many components
    // subscribe to presence events (e.g., in member lists)
    const originalGetUser = mx.getUser.bind(mx);
    mx.getUser = (userId: string) => {
      const user = originalGetUser(userId);
      if (user) {
        user.setMaxListeners(50);
      }
      return user;
    };

    // Setup handler to detect and auto-fix one-time key conflicts
    setupKeyConflictHandler(mx, session);

    // Register this as the global MatrixClient so auth utils can access the latest token
    const { setGlobalMatrixClient } = await import('../app/utils/auth');
    setGlobalMatrixClient(mx);
    console.log('[initMatrix] Registered global MatrixClient for auth utils');

    return mx;
  } catch (error: any) {
    // Handle crypto store account mismatch error
    if (error && error.message && error.message.includes("account in the store doesn't match")) {
      console.error('[initMatrix] ❌ Crypto store account mismatch detected!');
      console.error('[initMatrix] Error details:', error.message);
      console.error('[initMatrix] Expected account:', session.userId, session.deviceId);
      
      // This shouldn't happen anymore since we delete the shared db before init
      // If it does happen, it means there's a different issue
      throw new Error(
        `❌ Crypto store conflict detected.\n\n` +
        `This account's crypto store contains data from a different account.\n` +
        `Please clear site data in DevTools (F12 → Application → Storage → Clear site data)`
      );
    }
    
    // Re-throw other errors
    throw error;
  }
};

export const startClient = async (mx: MatrixClient) => {
  await mx.startClient({
    lazyLoadMembers: true,
  });
};

export const clearCacheAndReload = async (mx: MatrixClient) => {
  mx.stopClient();
  clearNavToActivePathStore(mx.getSafeUserId());
  await mx.store.deleteAllData();
  window.location.reload();
};

/**
 * Fix one-time key conflicts by clearing the Rust crypto database.
 * Call this when you see repeated "One time key already exists" errors.
 * The user will need to re-verify their devices after this.
 */
export const fixKeyConflict = async (mx: MatrixClient): Promise<void> => {
  const userId = mx.getSafeUserId();
  const deviceId = mx.getDeviceId();
  
  if (!userId || !deviceId) {
    console.error('[initMatrix] Cannot fix key conflict: missing userId or deviceId');
    return;
  }
  
  console.log('[initMatrix] 🔧 Fixing key conflict for', userId, deviceId);
  
  // Stop the client first
  mx.stopClient();
  
  // Delete the crypto databases
  const dbNames = await getAllIndexedDBNames();
  const storagePrefix = `${userId}@${deviceId}`;
  
  const cryptoDbNames = dbNames.filter(name => 
    name.includes('matrix-sdk-crypto') ||
    name.includes(storagePrefix) ||
    name.includes('crypto')
  );
  
  console.log('[initMatrix] 🗑️ Deleting crypto databases:', cryptoDbNames);
  
  for (const dbName of cryptoDbNames) {
    try {
      await deleteIndexedDB(dbName);
      console.log(`[initMatrix] ✅ Deleted: ${dbName}`);
    } catch (error) {
      console.error(`[initMatrix] ❌ Failed to delete ${dbName}:`, error);
    }
  }
  
  // Reload the page
  console.log('[initMatrix] 🔄 Reloading...');
  window.location.reload();
};

export const logoutClient = async (mx: MatrixClient) => {
  mx.stopClient();
  
  // Clear the global MatrixClient reference
  const { setGlobalMatrixClient } = await import('../app/utils/auth');
  setGlobalMatrixClient(null);
  
  try {
    await mx.logout();
  } catch {
    // ignore if failed to logout
  }
  await mx.clearStores();
  
  // Import sessions management  
  const { sessionsAtom, currentSessionAtom, getSessionByUserId, getCurrentSession } = await import('../app/state/sessions');
  const { getLocalStorageItem, setLocalStorageItem } = await import('../app/state/utils/atomWithLocalStorage');
  
  const userId = mx.getSafeUserId();
  const allSessions = getLocalStorageItem('matrixSessions', []);
  const currentUserId = getLocalStorageItem('currentSessionUserId', undefined);
  
  // Remove the current session
  const remainingSessions = allSessions.filter((s: any) => s.userId !== userId);
  setLocalStorageItem('matrixSessions', remainingSessions);
  
  // If we're logging out the current session and there are other sessions, switch to the first one
  if (remainingSessions.length > 0 && currentUserId === userId) {
    setLocalStorageItem('currentSessionUserId', remainingSessions[0].userId);
    window.location.reload();
  } else if (remainingSessions.length === 0) {
    // No more sessions, clear everything
    window.localStorage.clear();
    window.location.reload();
  } else {
    // We logged out a non-current session, just reload
    window.location.reload();
  }
};

export const logoutAllSessions = async () => {
  window.localStorage.clear();
  
  // Clear all IndexedDB databases
  const dbs = await window.indexedDB.databases();
  for (const idbInfo of dbs) {
    if (idbInfo.name) {
      window.indexedDB.deleteDatabase(idbInfo.name);
    }
  }
  
  window.location.reload();
};

export const clearLoginData = async () => {
  const dbs = await window.indexedDB.databases();

  dbs.forEach((idbInfo) => {
    const { name } = idbInfo;
    if (name) {
      window.indexedDB.deleteDatabase(name);
    }
  });

  window.localStorage.clear();
  window.location.reload();
};
