// File: monorepo/web/maplefile-frontend/src/services/Crypto/TagCryptoService.js
// Tag-specific encryption operations following E2EE architecture

class TagCryptoService {
  constructor() {
    this.isInitialized = false;
    this.sodium = null;

    // In-memory cache for tag keys (NEVER stored in localStorage)
    this._tagKeyCache = new Map();

    console.log("[TagCryptoService] Tag crypto service initialized");
  }

  // Initialize the crypto service
  async initialize() {
    if (this.isInitialized) return;

    try {
      // Initialize the main crypto service
      const { default: CryptoService } = await import("./CryptoService.js");
      await CryptoService.initialize();

      // Initialize libsodium directly
      const sodium = await import("libsodium-wrappers-sumo");
      await sodium.ready;
      this.sodium = sodium.default;

      this.cryptoService = CryptoService;
      this.isInitialized = true;

      console.log(
        "[TagCryptoService] Tag crypto service initialized successfully",
      );
    } catch (error) {
      console.error("[TagCryptoService] Failed to initialize:", error);
      throw new Error(`Failed to initialize TagCryptoService: ${error.message}`);
    }
  }

  // === Password Management via PasswordStorageService ===

  // Get user password from PasswordStorageService
  async getUserPassword() {
    try {
      const { default: passwordStorageService } = await import(
        "../PasswordStorageService.js"
      );
      const password = passwordStorageService.getPassword();

      if (!password) {
        throw new Error(
          "No password available in PasswordStorageService. Please log in again.",
        );
      }

      console.log(
        "[TagCryptoService] Retrieved password from PasswordStorageService",
      );
      return password;
    } catch (error) {
      console.error(
        "[TagCryptoService] Failed to get password from PasswordStorageService:",
        error,
      );
      throw error;
    }
  }

  // === Tag Key Generation ===

  // Generate a new 32-byte tag key
  generateTagKey() {
    if (!this.isInitialized || !this.sodium) {
      throw new Error("TagCryptoService not initialized");
    }

    return this.sodium.randombytes_buf(32);
  }

  // === Tag Name Encryption/Decryption ===

  // Encrypt tag name with tag key
  encryptTagName(name, tagKey) {
    if (!tagKey) {
      throw new Error("Tag key is required");
    }

    if (!this.sodium) {
      throw new Error("TagCryptoService not initialized");
    }

    const encoder = new TextEncoder();
    const nameBytes = encoder.encode(name || "");

    // Generate nonce
    const nonce = this.sodium.randombytes_buf(
      this.sodium.crypto_secretbox_NONCEBYTES,
    );

    // Encrypt with ChaCha20-Poly1305
    const ciphertext = this.sodium.crypto_secretbox_easy(
      nameBytes,
      nonce,
      tagKey,
    );

    // Return base64-encoded ciphertext and nonce
    return {
      ciphertext: this.sodium.to_base64(
        ciphertext,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      ),
      nonce: this.sodium.to_base64(
        nonce,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      ),
    };
  }

  // Decrypt tag name with tag key
  decryptTagName(encryptedData, tagKey) {
    if (!tagKey) {
      throw new Error("Tag key is required");
    }

    if (!this.sodium) {
      throw new Error("TagCryptoService not initialized");
    }

    try {
      // Decode base64
      const ciphertext = this.sodium.from_base64(
        encryptedData.ciphertext,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      );
      const nonce = this.sodium.from_base64(
        encryptedData.nonce,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      );

      // Decrypt
      const decrypted = this.sodium.crypto_secretbox_open_easy(
        ciphertext,
        nonce,
        tagKey,
      );

      // Decode UTF-8
      const decoder = new TextDecoder();
      return decoder.decode(decrypted);
    } catch (error) {
      console.error("[TagCryptoService] Failed to decrypt tag name:", error);
      throw new Error("Failed to decrypt tag name");
    }
  }

  // === Tag Color Encryption/Decryption ===

  // Encrypt tag color with tag key
  encryptTagColor(color, tagKey) {
    if (!tagKey) {
      throw new Error("Tag key is required");
    }

    if (!this.sodium) {
      throw new Error("TagCryptoService not initialized");
    }

    const encoder = new TextEncoder();
    const colorBytes = encoder.encode(color || "");

    // Generate nonce
    const nonce = this.sodium.randombytes_buf(
      this.sodium.crypto_secretbox_NONCEBYTES,
    );

    // Encrypt with ChaCha20-Poly1305
    const ciphertext = this.sodium.crypto_secretbox_easy(
      colorBytes,
      nonce,
      tagKey,
    );

    // Return base64-encoded ciphertext and nonce
    return {
      ciphertext: this.sodium.to_base64(
        ciphertext,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      ),
      nonce: this.sodium.to_base64(
        nonce,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      ),
    };
  }

  // Decrypt tag color with tag key
  decryptTagColor(encryptedData, tagKey) {
    if (!tagKey) {
      throw new Error("Tag key is required");
    }

    if (!this.sodium) {
      throw new Error("TagCryptoService not initialized");
    }

    try {
      // Decode base64
      const ciphertext = this.sodium.from_base64(
        encryptedData.ciphertext,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      );
      const nonce = this.sodium.from_base64(
        encryptedData.nonce,
        this.sodium.base64_variants.URLSAFE_NO_PADDING,
      );

      // Decrypt
      const decrypted = this.sodium.crypto_secretbox_open_easy(
        ciphertext,
        nonce,
        tagKey,
      );

      // Decode UTF-8
      const decoder = new TextDecoder();
      return decoder.decode(decrypted);
    } catch (error) {
      console.error("[TagCryptoService] Failed to decrypt tag color:", error);
      throw new Error("Failed to decrypt tag color");
    }
  }

  // === User Keys Management ===

  // Decrypt user keys with password from PasswordStorageService
  async decryptUserKeysWithPassword() {
    try {
      await this.initialize();

      // Get password from PasswordStorageService automatically
      const password = await this.getUserPassword();

      // Get stored encrypted user data
      const { default: LocalStorageService } = await import(
        "../Storage/LocalStorageService.js"
      );

      const encryptedData = LocalStorageService.getUserEncryptedData();

      if (
        !encryptedData.salt ||
        !encryptedData.encryptedMasterKey
      ) {
        throw new Error("Missing encrypted user data. Please log in again.");
      }

      console.log(
        "[TagCryptoService] Decrypting user keys with password from PasswordStorageService",
      );

      // Decode the encrypted data
      const salt = this.cryptoService.tryDecodeBase64(encryptedData.salt);
      const encryptedMasterKey = this.cryptoService.tryDecodeBase64(
        encryptedData.encryptedMasterKey,
      );

      // Derive key encryption key from password
      const keyEncryptionKey = await this.cryptoService.deriveKeyFromPassword(
        password,
        salt,
      );

      // Decrypt master key with KEK
      const masterKey = this.cryptoService.decryptWithSecretBox(
        encryptedMasterKey,
        keyEncryptionKey,
      );

      console.log(
        "[TagCryptoService] User keys decrypted successfully using PasswordStorageService",
      );

      return {
        masterKey,
        keyEncryptionKey,
      };
    } catch (error) {
      console.error(
        "[TagCryptoService] Failed to decrypt user keys:",
        error,
      );
      throw new Error(
        `Failed to decrypt keys: ${error.message}. Please check your password or log in again.`,
      );
    }
  }

  // Get user's encryption keys (using session storage or PasswordStorageService)
  async getUserKeys() {
    // First check if we have session keys in memory
    const { default: LocalStorageService } = await import(
      "../Storage/LocalStorageService.js"
    );

    const sessionKeys = LocalStorageService.getSessionKeys();

    if (sessionKeys.masterKey) {
      console.log("[TagCryptoService] Using session keys from memory");
      return {
        masterKey: sessionKeys.masterKey,
      };
    }

    // If no session keys, decrypt keys using PasswordStorageService
    console.log(
      "[TagCryptoService] No session keys found, decrypting with PasswordStorageService",
    );
    return await this.decryptUserKeysWithPassword();
  }

  // === Tag Key Encryption/Decryption with Master Key ===

  // Encrypt tag key with user's master key (matching Collection pattern)
  async encryptTagKey(tagKey, userMasterKey) {
    if (!tagKey || !userMasterKey) {
      throw new Error("Tag key and user master key are required");
    }

    if (!this.sodium) {
      throw new Error("TagCryptoService not initialized");
    }

    try {
      // Generate nonce
      const nonce = this.sodium.randombytes_buf(
        this.sodium.crypto_secretbox_NONCEBYTES,
      );

      // Encrypt tag key with master key (ChaCha20-Poly1305)
      const encrypted = this.sodium.crypto_secretbox_easy(
        tagKey,
        nonce,
        userMasterKey,
      );

      // Combine nonce + ciphertext for storage (matching Collection format)
      const combined = new Uint8Array(nonce.length + encrypted.length);
      combined.set(nonce, 0);
      combined.set(encrypted, nonce.length);

      // Return structure expected by API with base64 strings (matching Collection format)
      return {
        ciphertext: this.sodium.to_base64(
          combined,
          this.sodium.base64_variants.ORIGINAL,
        ), // Standard base64 for Go!
        nonce: this.sodium.to_base64(nonce, this.sodium.base64_variants.ORIGINAL), // Standard base64 for Go!
        key_version: 1,
        rotated_at: new Date().toISOString(),
        previous_keys: [],
      };
    } catch (error) {
      console.error("[TagCryptoService] Failed to encrypt tag key:", error);
      throw new Error("Failed to encrypt tag key");
    }
  }

  // Decrypt tag key with user's master key (matching Collection pattern)
  async decryptTagKey(encryptedKeyData, userMasterKey) {
    if (!encryptedKeyData || !userMasterKey) {
      throw new Error("Encrypted key data and user master key are required");
    }

    if (!this.sodium) {
      throw new Error("TagCryptoService not initialized");
    }

    try {
      // Decode from base64 (matching Collection format logic)
      let combined;

      // Handle different formats - some APIs store nonce+ciphertext together
      if (typeof encryptedKeyData.ciphertext === "string") {
        combined = this.cryptoService.tryDecodeBase64(
          encryptedKeyData.ciphertext,
        );
      } else if (Array.isArray(encryptedKeyData.ciphertext)) {
        // Legacy format - convert array to Uint8Array
        const ciphertext = new Uint8Array(encryptedKeyData.ciphertext);
        const nonce = new Uint8Array(encryptedKeyData.nonce);
        combined = new Uint8Array(nonce.length + ciphertext.length);
        combined.set(nonce, 0);
        combined.set(ciphertext, nonce.length);
      } else {
        throw new Error("Invalid encrypted key format");
      }

      // Extract nonce and ciphertext
      const nonceLength = this.sodium.crypto_secretbox_NONCEBYTES;
      const nonce = combined.slice(0, nonceLength);
      const ciphertext = combined.slice(nonceLength);

      console.log(
        `[TagCryptoService] Decrypting tag key - nonce: ${nonce.length}, ciphertext: ${ciphertext.length}`,
      );

      // Decrypt with master key
      const tagKey = this.sodium.crypto_secretbox_open_easy(
        ciphertext,
        nonce,
        userMasterKey,
      );

      return tagKey;
    } catch (error) {
      console.error("[TagCryptoService] Failed to decrypt tag key:", error);
      throw error;
    }
  }

  // === Cache Management ===

  // Cache decrypted tag key in memory
  cacheTagKey(tagId, tagKey) {
    this._tagKeyCache.set(tagId, tagKey);
    console.log(`[TagCryptoService] Cached tag key for tag ${tagId}`);
  }

  // Get cached tag key
  getCachedTagKey(tagId) {
    return this._tagKeyCache.get(tagId);
  }

  // Clear tag key from cache
  clearTagKeyCache(tagId) {
    if (tagId) {
      this._tagKeyCache.delete(tagId);
      console.log(`[TagCryptoService] Cleared tag key cache for tag ${tagId}`);
    } else {
      this._tagKeyCache.clear();
      console.log("[TagCryptoService] Cleared all tag key cache");
    }
  }

  // === Complete Tag Encryption/Decryption ===

  // Encrypt complete tag data (name and color)
  async encryptTag(tag) {
    await this.initialize();

    // Get user keys
    const userKeys = await this.getUserKeys();

    // Generate new tag key
    const tagKey = this.generateTagKey();

    // Encrypt name and color
    const encryptedName = this.encryptTagName(tag.name, tagKey);
    const encryptedColor = this.encryptTagColor(tag.color, tagKey);

    // Encrypt tag key with master key
    const encryptedTagKey = await this.encryptTagKey(tagKey, userKeys.masterKey);

    return {
      encrypted_name: encryptedName.ciphertext + ":" + encryptedName.nonce,
      encrypted_color:
        encryptedColor.ciphertext + ":" + encryptedColor.nonce,
      encrypted_tag_key: encryptedTagKey,
    };
  }

  // Decrypt complete tag data (name and color)
  async decryptTag(encryptedTag) {
    try {
      await this.initialize();

      console.log("[TagCryptoService] Decrypting tag:", {
        id: encryptedTag.id,
        hasEncryptedName: !!encryptedTag.encrypted_name,
        hasEncryptedColor: !!encryptedTag.encrypted_color,
        hasEncryptedKey: !!encryptedTag.encrypted_tag_key,
      });

      // Check cache first
      let tagKey = this.getCachedTagKey(encryptedTag.id);

      if (!tagKey) {
        // Get user keys
        const userKeys = await this.getUserKeys();

        // Decrypt tag key
        tagKey = await this.decryptTagKey(
          encryptedTag.encrypted_tag_key,
          userKeys.masterKey,
        );
        // Cache it
        this.cacheTagKey(encryptedTag.id, tagKey);
      }

      // Split encrypted name
      const [nameCiphertext, nameNonce] = encryptedTag.encrypted_name.split(":");
      if (!nameCiphertext || !nameNonce) {
        throw new Error("Invalid encrypted_name format. Expected 'ciphertext:nonce'");
      }
      const encryptedName = {
        ciphertext: nameCiphertext,
        nonce: nameNonce,
      };

      // Split encrypted color
      const [colorCiphertext, colorNonce] =
        encryptedTag.encrypted_color.split(":");
      if (!colorCiphertext || !colorNonce) {
        throw new Error("Invalid encrypted_color format. Expected 'ciphertext:nonce'");
      }
      const encryptedColor = {
        ciphertext: colorCiphertext,
        nonce: colorNonce,
      };

      // Decrypt name and color
      const name = this.decryptTagName(encryptedName, tagKey);
      const color = this.decryptTagColor(encryptedColor, tagKey);

      return {
        ...encryptedTag,
        name,
        color,
      };
    } catch (error) {
      console.error("[TagCryptoService] Failed to decrypt tag:", {
        tagId: encryptedTag?.id,
        error: error.message,
        encrypted_name: encryptedTag?.encrypted_name,
        encrypted_color: encryptedTag?.encrypted_color,
      });
      throw error;
    }
  }
}

// Export singleton instance
const tagCryptoService = new TagCryptoService();
export default tagCryptoService;
