/**
 * EncryptID Linked Wallet Store
 *
 * Client-side encrypted store for externally linked wallets (MetaMask,
 * Rainbow, pre-existing Safes). Follows the same AES-256-GCM pattern
 * as WalletStore (wallet-store.ts).
 *
 * Privacy model: Server stores only encrypted blobs + keccak256(address)
 * for dedup. All cleartext data is decrypted client-side only.
 */

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

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

export interface LinkedWalletEntry {
  id: string;
  address: string;
  type: 'eoa' | 'safe';
  chainIds: number[];
  label: string;
  isDefault: boolean;
  addedAt: number;
  verifiedAt: number;
  providerRdns?: string;
  providerName?: string;
  safeInfo?: {
    threshold: number;
    ownerCount: number;
    isEncryptIdOwner: boolean;
  };
}

interface StoredLinkedWalletData {
  version: 1;
  wallets: LinkedWalletEntry[];
}

interface PersistedBlob {
  c: string;
  iv: string;
}

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

const STORAGE_KEY = 'encryptid_linked_wallets';

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

export class LinkedWalletStore {
  private encryptionKey: CryptoKey;
  private cache: LinkedWalletEntry[] | null = null;

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

  async list(): Promise<LinkedWalletEntry[]> {
    if (this.cache) return [...this.cache];
    const wallets = await this.load();
    this.cache = wallets;
    return [...wallets];
  }

  async getDefault(): Promise<LinkedWalletEntry | null> {
    const wallets = await this.list();
    return wallets.find(w => w.isDefault) || wallets[0] || null;
  }

  async get(address: string): Promise<LinkedWalletEntry | null> {
    const wallets = await this.list();
    return wallets.find(
      w => w.address.toLowerCase() === address.toLowerCase(),
    ) || null;
  }

  async getById(id: string): Promise<LinkedWalletEntry | null> {
    const wallets = await this.list();
    return wallets.find(w => w.id === id) || null;
  }

  async add(entry: Omit<LinkedWalletEntry, 'id' | 'isDefault' | 'addedAt'> & {
    isDefault?: boolean;
  }): Promise<LinkedWalletEntry> {
    const wallets = await this.list();

    // Dedup by address
    const existing = wallets.find(
      w => w.address.toLowerCase() === entry.address.toLowerCase(),
    );
    if (existing) {
      existing.label = entry.label;
      existing.chainIds = entry.chainIds;
      existing.providerRdns = entry.providerRdns;
      existing.providerName = entry.providerName;
      existing.verifiedAt = entry.verifiedAt;
      if (entry.safeInfo) existing.safeInfo = entry.safeInfo;
      if (entry.isDefault) {
        wallets.forEach(w => w.isDefault = false);
        existing.isDefault = true;
      }
      await this.save(wallets);
      return { ...existing };
    }

    const wallet: LinkedWalletEntry = {
      id: crypto.randomUUID(),
      address: entry.address,
      type: entry.type,
      chainIds: entry.chainIds,
      label: entry.label,
      isDefault: entry.isDefault ?? wallets.length === 0,
      addedAt: Date.now(),
      verifiedAt: entry.verifiedAt,
      providerRdns: entry.providerRdns,
      providerName: entry.providerName,
      safeInfo: entry.safeInfo,
    };

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

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

  async update(id: string, updates: Partial<Pick<LinkedWalletEntry, 'label' | 'isDefault' | 'chainIds' | 'safeInfo'>>): Promise<LinkedWalletEntry | 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.chainIds !== undefined) wallet.chainIds = updates.chainIds;
    if (updates.safeInfo !== undefined) wallet.safeInfo = updates.safeInfo;
    if (updates.isDefault) {
      wallets.forEach(w => w.isDefault = false);
      wallet.isDefault = true;
    }

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

  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 (wasDefault && wallets.length > 0) {
      wallets[0].isDefault = true;
    }

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

  async clear(): Promise<void> {
    this.cache = [];
    try {
      localStorage.removeItem(STORAGE_KEY);
    } catch {}
  }

  // ==========================================================================
  // ENCRYPTION HELPERS
  // ==========================================================================

  async encryptEntry(entry: LinkedWalletEntry): Promise<{ ciphertext: string; iv: string }> {
    const json = JSON.stringify(entry);
    const encrypted = await encryptData(this.encryptionKey, json);
    return {
      ciphertext: bufferToBase64url(encrypted.ciphertext),
      iv: bufferToBase64url(encrypted.iv.buffer as ArrayBuffer),
    };
  }

  async decryptEntry(ciphertext: string, iv: string): Promise<LinkedWalletEntry> {
    const encrypted: EncryptedData = {
      ciphertext: base64urlToBuffer(ciphertext),
      iv: new Uint8Array(base64urlToBuffer(iv)),
    };
    const json = await decryptDataAsString(this.encryptionKey, encrypted);
    return JSON.parse(json);
  }

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

  private async load(): Promise<LinkedWalletEntry[]> {
    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: StoredLinkedWalletData = JSON.parse(json);

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

      return data.wallets;
    } catch (err) {
      console.warn('EncryptID LinkedWalletStore: Failed to load wallets', err);
      return [];
    }
  }

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

    const data: StoredLinkedWalletData = { 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 LinkedWalletStore: Failed to save wallets', err);
    }
  }
}

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

let linkedWalletStoreInstance: LinkedWalletStore | null = null;

export function getLinkedWalletStore(encryptionKey: CryptoKey): LinkedWalletStore {
  if (!linkedWalletStoreInstance) {
    linkedWalletStoreInstance = new LinkedWalletStore(encryptionKey);
  }
  return linkedWalletStoreInstance;
}

export function resetLinkedWalletStore(): void {
  linkedWalletStoreInstance = null;
}

// ============================================================================
// UTILITIES
// ============================================================================

export async function hashAddress(address: string, userId?: string): Promise<string> {
  // Salt with userId to prevent cross-user address correlation
  const normalized = (userId ? userId + ':' : '') + address.toLowerCase();
  const encoded = new TextEncoder().encode(normalized);
  const hash = await crypto.subtle.digest('SHA-256', encoded);
  return bufferToBase64url(hash);
}
