/**
 * EncryptID Client-Side Wallet Store
 *
 * Stores Safe wallet associations in encrypted localStorage using the
 * same AES-256-GCM encryption key derived from the passkey PRF.
 *
 * Privacy model: The server never sees which Safes you own. It only knows
 * you have "wallet capability" (a boolean flag). All Safe addresses, chain
 * IDs, labels, and preferences are encrypted at rest in the browser.
 *
 * Data is re-encrypted on every write with a fresh IV.
 */

import { encryptData, decryptDataAsString, type EncryptedData } from './key-derivation';
import { bufferToBase64url, base64urlToBuffer } from './webauthn';

// ============================================================================
// TYPES
// ============================================================================

export interface SafeWalletEntry {
  /** Client-generated UUID */
  id: string;
  /** Checksummed Safe contract address */
  safeAddress: string;
  /** EVM chain ID (e.g. 84532 for Base Sepolia, 8453 for Base) */
  chainId: number;
  /** Passkey-derived EOA address that is an owner on this Safe */
  eoaAddress: string;
  /** User-assigned label */
  label: string;
  /** Whether this is the user's default wallet */
  isDefault: boolean;
  /** Timestamp when this association was created */
  addedAt: number;
}

interface StoredWalletData {
  /** Schema version for future migrations */
  version: 1;
  /** Encrypted wallet entries */
  wallets: SafeWalletEntry[];
}

interface PersistedBlob {
  /** Base64url-encoded AES-256-GCM ciphertext */
  c: string;
  /** Base64url-encoded 12-byte IV */
  iv: string;
}

// ============================================================================
// CONSTANTS
// ============================================================================

const STORAGE_KEY = 'encryptid_wallets';

// ============================================================================
// WALLET STORE
// ============================================================================

/**
 * Encrypted client-side wallet store.
 *
 * Usage:
 * ```ts
 * const keys = await getKeyManager().getKeys();
 * const store = new WalletStore(keys.encryptionKey);
 *
 * // Add a Safe
 * await store.add({
 *   safeAddress: '0x...',
 *   chainId: 84532,
 *   eoaAddress: keys.eoaAddress!,
 *   label: 'Treasury',
 * });
 *
 * // List all
 * const wallets = await store.list();
 *
 * // Get default
 * const primary = await store.getDefault();
 * ```
 */
export class WalletStore {
  private encryptionKey: CryptoKey;
  private cache: SafeWalletEntry[] | null = null;

  constructor(encryptionKey: CryptoKey) {
    this.encryptionKey = encryptionKey;
  }

  /**
   * List all stored Safe wallet associations.
   */
  async list(): Promise<SafeWalletEntry[]> {
    if (this.cache) return [...this.cache];

    const wallets = await this.load();
    this.cache = wallets;
    return [...wallets];
  }

  /**
   * Get the default wallet, or the first one if none is marked default.
   */
  async getDefault(): Promise<SafeWalletEntry | null> {
    const wallets = await this.list();
    return wallets.find(w => w.isDefault) || wallets[0] || null;
  }

  /**
   * Get a wallet by Safe address and chain.
   */
  async get(safeAddress: string, chainId: number): Promise<SafeWalletEntry | null> {
    const wallets = await this.list();
    return wallets.find(
      w => w.safeAddress.toLowerCase() === safeAddress.toLowerCase() && w.chainId === chainId,
    ) || null;
  }

  /**
   * Add a new Safe wallet association.
   */
  async add(entry: Omit<SafeWalletEntry, 'id' | 'isDefault' | 'addedAt'> & {
    isDefault?: boolean;
  }): Promise<SafeWalletEntry> {
    const wallets = await this.list();

    // Check for duplicate
    const existing = wallets.find(
      w => w.safeAddress.toLowerCase() === entry.safeAddress.toLowerCase()
        && w.chainId === entry.chainId,
    );
    if (existing) {
      // Update in place
      existing.label = entry.label;
      existing.eoaAddress = entry.eoaAddress;
      if (entry.isDefault) {
        wallets.forEach(w => w.isDefault = false);
        existing.isDefault = true;
      }
      await this.save(wallets);
      return { ...existing };
    }

    const wallet: SafeWalletEntry = {
      id: crypto.randomUUID(),
      safeAddress: entry.safeAddress,
      chainId: entry.chainId,
      eoaAddress: entry.eoaAddress,
      label: entry.label,
      isDefault: entry.isDefault ?? wallets.length === 0,  // First wallet is default
      addedAt: Date.now(),
    };

    if (wallet.isDefault) {
      wallets.forEach(w => w.isDefault = false);
    }

    wallets.push(wallet);
    await this.save(wallets);
    return { ...wallet };
  }

  /**
   * Update an existing wallet entry.
   */
  async update(id: string, updates: Partial<Pick<SafeWalletEntry, 'label' | 'isDefault'>>): Promise<SafeWalletEntry | null> {
    const wallets = await this.list();
    const wallet = wallets.find(w => w.id === id);
    if (!wallet) return null;

    if (updates.label !== undefined) wallet.label = updates.label;
    if (updates.isDefault) {
      wallets.forEach(w => w.isDefault = false);
      wallet.isDefault = true;
    }

    await this.save(wallets);
    return { ...wallet };
  }

  /**
   * Remove a wallet association.
   */
  async remove(id: string): Promise<boolean> {
    const wallets = await this.list();
    const idx = wallets.findIndex(w => w.id === id);
    if (idx === -1) return false;

    const wasDefault = wallets[idx].isDefault;
    wallets.splice(idx, 1);

    // If we removed the default, promote the first remaining
    if (wasDefault && wallets.length > 0) {
      wallets[0].isDefault = true;
    }

    await this.save(wallets);
    return true;
  }

  /**
   * Remove all wallet associations and clear storage.
   */
  async clear(): Promise<void> {
    this.cache = [];
    try {
      localStorage.removeItem(STORAGE_KEY);
    } catch {
      // Storage not available
    }
  }

  // ==========================================================================
  // PRIVATE: Encrypt/Decrypt localStorage
  // ==========================================================================

  private async load(): Promise<SafeWalletEntry[]> {
    try {
      const raw = localStorage.getItem(STORAGE_KEY);
      if (!raw) return [];

      const blob: PersistedBlob = JSON.parse(raw);
      if (!blob.c || !blob.iv) return [];

      const encrypted: EncryptedData = {
        ciphertext: base64urlToBuffer(blob.c),
        iv: new Uint8Array(base64urlToBuffer(blob.iv)),
      };

      const json = await decryptDataAsString(this.encryptionKey, encrypted);
      const data: StoredWalletData = JSON.parse(json);

      if (data.version !== 1) {
        console.warn('EncryptID WalletStore: Unknown schema version', data.version);
        return [];
      }

      return data.wallets;
    } catch (err) {
      // Decryption failure = wrong key (new passkey?) or corrupt data
      console.warn('EncryptID WalletStore: Failed to load wallets', err);
      return [];
    }
  }

  private async save(wallets: SafeWalletEntry[]): Promise<void> {
    this.cache = [...wallets];

    const data: StoredWalletData = { version: 1, wallets };
    const json = JSON.stringify(data);

    const encrypted = await encryptData(this.encryptionKey, json);

    const blob: PersistedBlob = {
      c: bufferToBase64url(encrypted.ciphertext),
      iv: bufferToBase64url(encrypted.iv.buffer as ArrayBuffer),
    };

    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(blob));
    } catch (err) {
      console.error('EncryptID WalletStore: Failed to save wallets', err);
    }
  }
}

// ============================================================================
// SINGLETON
// ============================================================================

let walletStoreInstance: WalletStore | null = null;

/**
 * Get or create the wallet store singleton.
 * Requires an encryption key (from the key manager).
 */
export function getWalletStore(encryptionKey: CryptoKey): WalletStore {
  if (!walletStoreInstance) {
    walletStoreInstance = new WalletStore(encryptionKey);
  }
  return walletStoreInstance;
}

/**
 * Reset the wallet store (e.g. on logout).
 */
export function resetWalletStore(): void {
  walletStoreInstance = null;
}
