/**
 * EncryptID Encrypted Account Vault
 *
 * Stores all account data (profile, emails, devices, addresses, wallets,
 * preferences) as a single JSON document, encrypted client-side with
 * AES-256-GCM using the DocCrypto key hierarchy, and persisted as an
 * opaque blob via the backup API. The server never sees plaintext.
 *
 * Key derivation:
 *   Master Key (from WebAuthn PRF)
 *     → deriveSpaceKey("__vault")
 *       → deriveDocKey(spaceKey, "account-vault")
 *         → AES-256-GCM encrypt/decrypt
 *
 * Any device with the user's passkey derives the same vault key
 * (deterministic HKDF), so no key sync is needed.
 */

import { DocCrypto } from '../../shared/local-first/crypto';

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

export interface AccountVault {
  version: 1;
  profile: { displayName: string | null; avatarUrl: string | null };
  emails: Array<{ address: string; verified: boolean; addedAt: number }>;
  devices: Array<{ credentialIdPrefix: string; name: string; platform: string; addedAt: number }>;
  addresses: Array<{
    id: string; label: string; street1: string; city: string;
    state: string; postalCode: string; country: string;
  }>;
  wallets: Array<{
    id: string; safeAddress: string; chainId: number;
    eoaAddress: string; label: string; addedAt: number;
  }>;
  preferences: Record<string, unknown>;
  createdAt: number;
  updatedAt: number;
}

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

const VAULT_SPACE = '__vault';
const VAULT_DOC = 'account-vault';
const CACHE_KEY = 'encryptid_vault_cache';
const TOKEN_KEY = 'encryptid_token';

// ============================================================================
// VaultManager
// ============================================================================

export class VaultManager {
  private crypto: DocCrypto;
  private vaultKey: CryptoKey | null = null;
  private vault: AccountVault | null = null;

  constructor(docCrypto: DocCrypto) {
    this.crypto = docCrypto;
  }

  /**
   * Derive the deterministic vault key.
   */
  private async getVaultKey(): Promise<CryptoKey> {
    if (!this.vaultKey) {
      this.vaultKey = await this.crypto.deriveDocKeyDirect(VAULT_SPACE, VAULT_DOC);
    }
    return this.vaultKey;
  }

  /**
   * Get the auth token for backup API calls.
   */
  private getToken(): string | null {
    try {
      return localStorage.getItem(TOKEN_KEY);
    } catch {
      return null;
    }
  }

  /**
   * Load vault from server (or localStorage cache if offline).
   * On 404: returns empty vault. Caches encrypted blob locally for offline.
   */
  async load(): Promise<AccountVault> {
    const key = await this.getVaultKey();
    const token = this.getToken();

    let packed: Uint8Array | null = null;

    // Try server first
    if (token) {
      try {
        const res = await fetch(`/api/backup/${VAULT_SPACE}/${VAULT_DOC}`, {
          headers: { 'Authorization': `Bearer ${token}` },
        });
        if (res.ok) {
          packed = new Uint8Array(await res.arrayBuffer());
          // Cache encrypted blob locally
          this.cacheBlob(packed);
        } else if (res.status !== 404) {
          console.warn('Vault: server returned', res.status);
        }
      } catch (err) {
        console.warn('Vault: server fetch failed, trying cache', err);
      }
    }

    // Fallback to localStorage cache
    if (!packed) {
      packed = this.loadCachedBlob();
    }

    if (!packed || packed.length < 13) {
      // No vault exists yet — return empty
      this.vault = VaultManager.empty();
      return this.vault;
    }

    // Decrypt
    const blob = DocCrypto.unpack(packed);
    const plainBytes = await this.crypto.decrypt(key, blob);
    const json = new TextDecoder().decode(plainBytes);
    this.vault = JSON.parse(json) as AccountVault;
    return this.vault;
  }

  /**
   * Save vault to server (and localStorage cache).
   */
  async save(): Promise<void> {
    if (!this.vault) return;

    const key = await this.getVaultKey();
    const token = this.getToken();
    if (!token) throw new Error('Vault: not authenticated');

    this.vault.updatedAt = Date.now();

    const json = JSON.stringify(this.vault);
    const plainBytes = new TextEncoder().encode(json);
    const blob = await this.crypto.encrypt(key, plainBytes);
    const packed = DocCrypto.pack(blob);

    // Upload to server
    const res = await fetch(`/api/backup/${VAULT_SPACE}/${VAULT_DOC}`, {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/octet-stream',
      },
      body: packed,
    });

    if (!res.ok) {
      const data = await res.json().catch(() => ({}));
      throw new Error((data as any).error || `Vault save failed (${res.status})`);
    }

    // Update local cache
    this.cacheBlob(packed);
  }

  /**
   * Get the in-memory vault (loads from server/cache if needed).
   */
  async get(): Promise<AccountVault> {
    if (!this.vault) {
      await this.load();
    }
    return this.vault!;
  }

  /**
   * Merge partial updates into the vault and auto-save.
   */
  async update(partial: Partial<Omit<AccountVault, 'version' | 'createdAt' | 'updatedAt'>>): Promise<AccountVault> {
    const v = await this.get();
    Object.assign(v, partial);
    await this.save();
    return v;
  }

  /**
   * Check if a vault exists on the server (without downloading full blob).
   */
  async exists(): Promise<boolean> {
    const token = this.getToken();
    if (!token) return false;
    try {
      const res = await fetch(`/api/backup/${VAULT_SPACE}/${VAULT_DOC}`, {
        method: 'GET',
        headers: { 'Authorization': `Bearer ${token}` },
      });
      return res.ok;
    } catch {
      return false;
    }
  }

  /**
   * Clear in-memory state (on logout). Does NOT delete server data.
   */
  clear(): void {
    this.vault = null;
    this.vaultKey = null;
  }

  // ==========================================================================
  // PRIVATE: localStorage cache
  // ==========================================================================

  private cacheBlob(packed: Uint8Array): void {
    try {
      // Store as base64
      const binary = String.fromCharCode(...packed);
      localStorage.setItem(CACHE_KEY, btoa(binary));
    } catch {
      // localStorage full or unavailable
    }
  }

  private loadCachedBlob(): Uint8Array | null {
    try {
      const b64 = localStorage.getItem(CACHE_KEY);
      if (!b64) return null;
      const binary = atob(b64);
      const bytes = new Uint8Array(binary.length);
      for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i);
      }
      return bytes;
    } catch {
      return null;
    }
  }

  // ==========================================================================
  // STATIC: empty vault
  // ==========================================================================

  static empty(): AccountVault {
    const now = Date.now();
    return {
      version: 1,
      profile: { displayName: null, avatarUrl: null },
      emails: [],
      devices: [],
      addresses: [],
      wallets: [],
      preferences: {},
      createdAt: now,
      updatedAt: now,
    };
  }
}

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

let vaultInstance: VaultManager | null = null;

/**
 * Get or create the vault manager singleton.
 * Requires a DocCrypto instance (from getDocBridge().getDocCrypto()).
 */
export function getVaultManager(docCrypto: DocCrypto): VaultManager {
  if (!vaultInstance) {
    vaultInstance = new VaultManager(docCrypto);
  }
  return vaultInstance;
}

/**
 * Reset the vault manager (on logout).
 */
export function resetVaultManager(): void {
  if (vaultInstance) {
    vaultInstance.clear();
    vaultInstance = null;
  }
}
