// AGPL-3.0-only
//
// This is free software for the public good of a permacomputer
// hosted at permacomputer.com, an always-on computer by the people, for the
// people. One which is durable, easy to repair, and distributed like tap water
// for machine learning intelligence.
//
// The permacomputer is community-owned infrastructure optimized around
// four values:
//
//   TRUTH    - First principles, math & science, open source code
//   FREEDOM  - Voluntary partnerships, freedom from tyranny & corporate control
//   HARMONY  - Minimal waste, self-renewing systems with diverse connections
//   LOVE     - Be yourself without hurting others, cooperation through natural law
//
// This software contributes to that vision by making machine learning
// accessible to everyone through a free, open, embeddable chat interface.
// Code is seeds to sprout on any abandoned technology.
//
// Learn more: https://www.permacomputer.com
//
// NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
//
// Copyright 2025-2026 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com

/**
 * UncloseAI Vault - Encrypted Settings Storage
 *
 * All settings are encrypted with a user-chosen password using AES-256.
 * Session persistence allows staying unlocked across page navigations.
 * Sessions persist indefinitely until the user explicitly locks.
 *
 * Requires CryptoJS library.
 *
 * Flow:
 *   1. User creates vault with password (first time)
 *   2. Settings encrypted with password, stored in localStorage
 *   3. Session key derived and stored (encrypted) for auto-unlock
 *   4. On page load, session key auto-unlocks (persists until locked)
 *   5. User can explicitly lock to end session
 */

const UncloseVault = {
  // localStorage keys
  VAULT_KEY: 'uncloseai_vault',
  SALT_KEY: 'uncloseai_device_salt',
  SESSION_KEY: 'uncloseai_session',

  // In-memory state
  _settings: null,
  _password: null,

  /**
   * Check if CryptoJS is available
   */
  isAvailable() {
    return typeof CryptoJS !== 'undefined';
  },

  /**
   * Get or create device-specific salt (32 bytes)
   */
  getDeviceSalt() {
    let salt = localStorage.getItem(this.SALT_KEY);
    if (!salt) {
      if (!this.isAvailable()) return null;
      const randomBytes = CryptoJS.lib.WordArray.random(32);
      salt = randomBytes.toString();
      localStorage.setItem(this.SALT_KEY, salt);
    }
    return salt;
  },

  /**
   * Generate vault ID from password (deterministic per device)
   */
  getVaultId(password) {
    if (!this.isAvailable()) return null;
    const salt = this.getDeviceSalt();
    return CryptoJS.SHA256(password + salt).toString();
  },

  /**
   * Encrypt data with key
   */
  encrypt(data, key) {
    if (!this.isAvailable()) return null;
    try {
      return CryptoJS.AES.encrypt(JSON.stringify(data), key).toString();
    } catch (e) {
      console.error('Vault encryption failed:', e);
      return null;
    }
  },

  /**
   * Decrypt data with key
   */
  decrypt(encryptedData, key) {
    if (!this.isAvailable()) return null;
    try {
      const bytes = CryptoJS.AES.decrypt(encryptedData, key);
      const decryptedStr = bytes.toString(CryptoJS.enc.Utf8);
      if (!decryptedStr) return null;
      return JSON.parse(decryptedStr);
    } catch (e) {
      return null;
    }
  },

  /**
   * Check if a vault exists
   */
  hasVault() {
    const vaultData = localStorage.getItem(this.VAULT_KEY);
    if (!vaultData) return false;
    try {
      const vault = JSON.parse(vaultData);
      return !!vault.encrypted_settings;
    } catch (e) {
      return false;
    }
  },

  /**
   * Check if vault is currently unlocked (in memory)
   */
  isUnlocked() {
    return this._settings !== null;
  },

  /**
   * Get vault data from localStorage
   */
  getVault() {
    try {
      const vaultJson = localStorage.getItem(this.VAULT_KEY);
      return vaultJson ? JSON.parse(vaultJson) : null;
    } catch (e) {
      return null;
    }
  },

  /**
   * Save vault data to localStorage
   */
  saveVault(vault) {
    localStorage.setItem(this.VAULT_KEY, JSON.stringify(vault));
  },

  /**
   * Create session for auto-unlock persistence
   * Stores the password encrypted with a random session key
   * Session persists per-origin in localStorage until explicitly locked
   */
  createSession(password) {
    if (!this.isAvailable()) return;

    try {
      // Generate random session key
      const sessionKey = CryptoJS.lib.WordArray.random(32).toString();

      // Encrypt password with session key
      const encryptedPassword = CryptoJS.AES.encrypt(password, sessionKey).toString();

      // Store session data in localStorage (per-origin, persists until locked)
      const sessionData = {
        key: sessionKey,
        data: encryptedPassword,
        created: Date.now()
      };

      localStorage.setItem(this.SESSION_KEY, JSON.stringify(sessionData));
    } catch (e) {
      console.error('Failed to create session:', e);
    }
  },

  /**
   * Try to restore session (auto-unlock)
   * Returns true if session was valid and vault unlocked
   * Session persists per-origin until explicitly locked
   */
  tryRestoreSession() {
    if (!this.isAvailable()) return false;
    if (this.isUnlocked()) return true; // Already unlocked

    try {
      const sessionJson = localStorage.getItem(this.SESSION_KEY);
      if (!sessionJson) return false;

      const session = JSON.parse(sessionJson);

      // Decrypt password from session
      const bytes = CryptoJS.AES.decrypt(session.data, session.key);
      const password = bytes.toString(CryptoJS.enc.Utf8);

      if (!password) {
        this.clearSession();
        return false;
      }

      // Try to unlock with recovered password
      const result = this._unlockWithPassword(password, false); // Don't recreate session

      if (result.success) {
        return true;
      }

      this.clearSession();
      return false;
    } catch (e) {
      console.error('Session restore failed:', e);
      this.clearSession();
      return false;
    }
  },

  /**
   * Clear session data
   */
  clearSession() {
    localStorage.removeItem(this.SESSION_KEY);
  },

  /**
   * Get session info (for UI display)
   */
  getSessionInfo() {
    try {
      const sessionJson = localStorage.getItem(this.SESSION_KEY);
      if (!sessionJson) return null;

      const session = JSON.parse(sessionJson);

      return {
        createdAt: new Date(session.created)
      };
    } catch (e) {
      return null;
    }
  },

  /**
   * Internal unlock with password
   */
  _unlockWithPassword(password, createNewSession = true) {
    if (!this.isAvailable()) {
      return { success: false, error: 'CryptoJS not available' };
    }

    const vault = this.getVault();
    if (!vault || !vault.encrypted_settings) {
      return { success: false, error: 'No vault found' };
    }

    const decrypted = this.decrypt(vault.encrypted_settings, password);
    if (!decrypted) {
      return { success: false, error: 'Wrong password' };
    }

    // Store in memory
    this._settings = decrypted;
    this._password = password;

    // Create session for persistence
    if (createNewSession) {
      this.createSession(password);
    }

    // Dispatch event
    window.dispatchEvent(new CustomEvent('uncloseai-vault-unlocked'));

    return { success: true };
  },

  /**
   * Create a new vault with password
   */
  create(password) {
    if (!this.isAvailable()) {
      return { success: false, error: 'CryptoJS not available. Include crypto-js library.' };
    }
    if (!password || password.length < 8) {
      return { success: false, error: 'Password must be at least 8 characters' };
    }

    if (this.hasVault()) {
      return { success: false, error: 'Vault already exists. Use unlock() instead.' };
    }

    // Create with default settings
    const defaultSettings = this.getDefaultSettings();
    const encrypted = this.encrypt(defaultSettings, password);

    if (!encrypted) {
      return { success: false, error: 'Encryption failed' };
    }

    const vault = {
      encrypted_settings: encrypted,
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString()
    };

    this.saveVault(vault);

    // Unlock in memory
    this._settings = defaultSettings;
    this._password = password;

    // Create session
    this.createSession(password);

    window.dispatchEvent(new CustomEvent('uncloseai-vault-created'));
    window.dispatchEvent(new CustomEvent('uncloseai-vault-unlocked'));

    return { success: true };
  },

  /**
   * Unlock vault with password
   */
  unlock(password) {
    if (!password) {
      return { success: false, error: 'Password required' };
    }
    return this._unlockWithPassword(password, true);
  },

  /**
   * Lock the vault (clear memory and session)
   */
  lock() {
    this._settings = null;
    this._password = null;
    this.clearSession();
    window.dispatchEvent(new CustomEvent('uncloseai-vault-locked'));
  },

  /**
   * Save current settings to vault
   */
  save() {
    if (!this.isUnlocked()) {
      console.error('Cannot save: vault not unlocked');
      return false;
    }

    const vault = this.getVault() || {};
    const encrypted = this.encrypt(this._settings, this._password);

    if (!encrypted) {
      console.error('Failed to encrypt settings');
      return false;
    }

    vault.encrypted_settings = encrypted;
    vault.updated_at = new Date().toISOString();
    this.saveVault(vault);

    return true;
  },

  /**
   * Get a setting value (returns default if vault locked)
   */
  get(key, defaultValue = null) {
    if (!this.isUnlocked()) {
      // Return from defaults if not unlocked
      const defaults = this.getDefaultSettings();
      return defaults[key] !== undefined ? defaults[key] : defaultValue;
    }
    return this._settings[key] !== undefined ? this._settings[key] : defaultValue;
  },

  /**
   * Set a setting value (requires unlocked vault)
   */
  set(key, value) {
    if (!this.isUnlocked()) {
      console.warn('Vault locked - setting not saved');
      return false;
    }
    this._settings[key] = value;
    return this.save();
  },

  /**
   * Set multiple settings at once
   */
  setMultiple(settingsObj) {
    if (!this.isUnlocked()) {
      console.warn('Vault locked - settings not saved');
      return false;
    }
    Object.assign(this._settings, settingsObj);
    return this.save();
  },

  /**
   * Get all settings
   */
  getAll() {
    if (!this.isUnlocked()) return this.getDefaultSettings();
    return { ...this._settings };
  },

  /**
   * Default settings (used when vault doesn't exist or is locked)
   */
  getDefaultSettings() {
    return {
      // Language
      language: 'en',

      // Model selection
      selectedModel: null,
      selectedEndpoint: null,
      selectedVoice: 'tts-1:onyx',

      // Custom API
      useCustomAPI: false,
      customBaseURL: '',
      customAPIKey: '',

      // Unsandbox code execution
      useUnsandbox: false,
      unsandboxPublicKey: '',
      unsandboxSecretKey: '',

      // UI preferences
      settingsOpen: false
    };
  },

  /**
   * Delete vault entirely (requires password confirmation)
   */
  deleteVault(password) {
    if (!this.isAvailable()) {
      return { success: false, error: 'CryptoJS not available' };
    }

    const vault = this.getVault();
    if (!vault) {
      return { success: false, error: 'No vault exists' };
    }

    // Verify password
    const decrypted = this.decrypt(vault.encrypted_settings, password);
    if (!decrypted) {
      return { success: false, error: 'Wrong password' };
    }

    // Clear everything
    localStorage.removeItem(this.VAULT_KEY);
    this.clearSession();
    this._settings = null;
    this._password = null;

    window.dispatchEvent(new CustomEvent('uncloseai-vault-deleted'));

    return { success: true };
  },

  /**
   * Change vault password
   */
  changePassword(currentPassword, newPassword) {
    if (!this.isAvailable()) {
      return { success: false, error: 'CryptoJS not available' };
    }
    if (!newPassword || newPassword.length < 8) {
      return { success: false, error: 'New password must be at least 8 characters' };
    }

    const vault = this.getVault();
    if (!vault) {
      return { success: false, error: 'No vault exists' };
    }

    // Decrypt with current password
    const decrypted = this.decrypt(vault.encrypted_settings, currentPassword);
    if (!decrypted) {
      return { success: false, error: 'Wrong current password' };
    }

    // Re-encrypt with new password
    const encrypted = this.encrypt(decrypted, newPassword);
    if (!encrypted) {
      return { success: false, error: 'Re-encryption failed' };
    }

    vault.encrypted_settings = encrypted;
    vault.updated_at = new Date().toISOString();
    this.saveVault(vault);

    // Update in-memory and session
    this._password = newPassword;
    this.createSession(newPassword);

    return { success: true };
  },

  /**
   * Initialize - try to restore session on page load
   * Call this when the page loads
   */
  init() {
    if (!this.isAvailable()) {
      console.warn('UncloseVault: CryptoJS not available');
      return false;
    }

    if (this.hasVault()) {
      // Try auto-unlock from session
      if (this.tryRestoreSession()) {
        console.log('UncloseVault: Session restored, vault unlocked');
        return true;
      }
      console.log('UncloseVault: Vault exists but locked (no valid session)');
      return false;
    }

    console.log('UncloseVault: No vault exists yet');
    return false;
  }
};

// Export for ES modules
export { UncloseVault };

// Also make available globally
if (typeof window !== 'undefined') {
  window.UncloseVault = UncloseVault;
}
