import { Platform } from 'react-native';
import { 
  TokenStorage, 
  DPOP_STORAGE_KEYS, 
  AUTH_STORAGE_KEYS 
} from '@explorins/pers-sdk/core';
import { AsyncStorageTokenStorage } from './async-storage-token-storage';

// Conditionally require Keychain to avoid Web bundler errors
let Keychain: any;
if (Platform.OS !== 'web') {
  try {
    Keychain = require('react-native-keychain');
  } catch (e) {
    console.warn('ReactNativeSecureStorage: Failed to load react-native-keychain', e);
  }
}

/**
 * Secure Storage implementation for React Native
 * Uses Keychain/Keystore for sensitive data and AsyncStorage for non-sensitive data.
 */
export class ReactNativeSecureStorage implements TokenStorage {
  // Set to false because Keychain stores strings, so we need extractable (serializable) keys
  readonly supportsObjects = false;
  
  // Define which keys MUST go to secure hardware storage
  private readonly SECURE_KEYS = new Set([
     DPOP_STORAGE_KEYS.PRIVATE,
     AUTH_STORAGE_KEYS.ACCESS_TOKEN,
     AUTH_STORAGE_KEYS.REFRESH_TOKEN,
     AUTH_STORAGE_KEYS.PROVIDER_TOKEN
  ]);

  private fallbackStorage: AsyncStorageTokenStorage;

  constructor(private keyPrefix: string = 'pers_tokens_') {
    this.fallbackStorage = new AsyncStorageTokenStorage(keyPrefix);
  }

  async set(key: string, value: unknown): Promise<void> {
    const prefixedKey = this.getKeyName(key);
    const stringValue = typeof value === 'string' ? value : JSON.stringify(value);

    // Cast key to any to bypass strict literal type checking of the Set.has method
    // In runtime, 'key' is just a string, so this is safe.
    if (this.SECURE_KEYS.has(key as any)) {
      // Store in Keychain/Keystore
      try {
        if (Keychain) {
          // Service name acts as the key identifier in simple usage
          await Keychain.setGenericPassword(prefixedKey, stringValue, { service: prefixedKey });
        } else {
          throw new Error('RN Keychain not available');
        }
      } catch (e) {
        console.warn(`[ReactNativeSecureStorage] Keychain set failed for ${key}, falling back to AsyncStorage`, e);
        // Fallback to AsyncStorage if Keychain fails
        await this.fallbackStorage.set(key, stringValue);
      }
    } else {
      // Store standard config/metadata in AsyncStorage
      await this.fallbackStorage.set(key, stringValue);
    }
  }

  async get(key: string): Promise<unknown | null> {
    const prefixedKey = this.getKeyName(key);

    if (this.SECURE_KEYS.has(key as any)) {
      try {
        if (Keychain) {
          const credentials = await Keychain.getGenericPassword({ service: prefixedKey });
          if (credentials) {
            return this.tryParse(credentials.password);
          }
        }
      } catch (e) {
        console.warn(`[ReactNativeSecureStorage] Failed to access keychain for ${key}`, e);
        // Continue to fallback check...
      }
      
      // Fallback: Check AsyncStorage if not found in Keychain or Keychain failed
      try {
        const val = await this.fallbackStorage.get(key);
        return val ? this.tryParse(val) : null;
      } catch (e) {
        return null;
      }
    } else {
      try {
        const val = await this.fallbackStorage.get(key);
        return val ? this.tryParse(val) : null;
      } catch (e) {
        console.warn(`[ReactNativeSecureStorage] Failed to access AsyncStorage for ${key}`, e);
        return null;
      }
    }
  }

  async remove(key: string): Promise<void> {
    const prefixedKey = this.getKeyName(key);

    if (this.SECURE_KEYS.has(key as any)) {
      try {
        if (Keychain) {
          await Keychain.resetGenericPassword({ service: prefixedKey });
        }
      } catch (e) {
        console.warn(`[ReactNativeSecureStorage] Failed to reset keychain for ${key}`, e);
      }
      // Always remove from fallback storage too, just in case
      await this.fallbackStorage.remove(key);
    } else {
      await this.fallbackStorage.remove(key);
    }
  }

  async clear(): Promise<void> {
    const clearPromises: Promise<void>[] = [];
    
    // Clear all known secure keys with retry logic
    for (const key of this.SECURE_KEYS) {
      clearPromises.push(
        (async () => {
          try {
            if (Keychain) {
              await Keychain.resetGenericPassword({ service: this.getKeyName(key) });
            }
          } catch (e) {
            console.warn(`[ReactNativeSecureStorage] Retry clearing ${key}`);
            // Retry once
            try {
              if (Keychain) {
                await Keychain.resetGenericPassword({ service: this.getKeyName(key) });
              }
            } catch (retryError) {
              console.error(`[ReactNativeSecureStorage] Failed to clear ${key} after retry:`, retryError);
            }
          }
        })()
      );
    }
    
    // Wait for all Keychain clearing to complete (or fail)
    await Promise.allSettled(clearPromises);
    
    // Always clear AsyncStorage fallback
    try {
      await this.fallbackStorage.clear();
    } catch (e) {
      console.error('[ReactNativeSecureStorage] Failed to clear AsyncStorage:', e);
    }
  }

  private getKeyName(key: string): string {
    // For Keychain, we might want to avoid prefixes if we want to share across apps, 
    // but for simple isolation, prefix is fine to avoid collisions if multiple instances exist.
    // However, Keychain services are global to the app bundle ID usually.
    return `${this.keyPrefix}${key}`;
  }

  private tryParse(val: string): unknown {
      try {
          return JSON.parse(val);
      } catch {
          return val;
      }
  }
}
