import {Injectable} from '@angular/core';
import {StorageProvider} from './storage.provider';
import {Capacitor} from '@capacitor/core';
import {SecureStoragePlugin} from 'capacitor-secure-storage-plugin';

@Injectable({providedIn: 'root'})
export class EncryptedStorageProvider {
  constructor(private storageProvider: StorageProvider) {}

  /**
   * Retrieve a large value from an encrypted storage
   * Also returns undefined if a secure context is not available (i.e. web).
   * @param key Unique identifier of the wanted resource in storage
   * @returns The value of the resource, if found
   */
  async get<T>(key: string): Promise<T | undefined> {
    if (!Capacitor.isNativePlatform()) return undefined;

    try {
      const jwt = JSON.parse((await SecureStoragePlugin.get({key: `stapps:key:${key}`})).value);
      const aesKey = await crypto.subtle.importKey('jwk', jwt, {name: 'AES-GCM'}, true, [
        'encrypt',
        'decrypt',
      ]);
      const iv = await this.storageProvider.get<ArrayBuffer>(`encrypted:${key}:iv`);

      const encryptedIdCards = await this.storageProvider.get<ArrayBuffer>(`encrypted:${key}`);
      const decrypted = await crypto.subtle.decrypt({name: 'AES-GCM', iv}, aesKey, encryptedIdCards);

      const decompressionStream = new DecompressionStream('gzip');
      const writer = decompressionStream.writable.getWriter();
      writer.write(decrypted);
      writer.close();

      const decompressed = await new Response(decompressionStream.readable).arrayBuffer();
      return JSON.parse(new TextDecoder().decode(decompressed));
    } catch (error) {
      console.warn(error);
      return undefined;
    }
  }

  /**
   * Store a large value in an encrypted storage
   * Does nothing if a secure context is not available (i.e. web).
   * @param key Unique identifier of the resource in storage
   * @param value The value to store
   * @returns A promise that resolves when the value is stored
   */
  async set<T>(key: string, value: T): Promise<void> {
    if (!Capacitor.isNativePlatform()) return undefined;

    try {
      const compressionStream = new CompressionStream('gzip');
      const writer = compressionStream.writable.getWriter();
      writer.write(new TextEncoder().encode(JSON.stringify(value)));
      writer.close();
      const encoded = await new Response(compressionStream.readable).arrayBuffer();

      const iv = crypto.getRandomValues(new Uint8Array(16));
      const aesKey = await crypto.subtle.generateKey({name: 'AES-GCM', length: 256}, true, [
        'encrypt',
        'decrypt',
      ]);

      await Promise.all([
        SecureStoragePlugin.set({
          key: `stapps:key:${key}`,
          value: JSON.stringify(await crypto.subtle.exportKey('jwk', aesKey)),
        }),
        this.storageProvider.put(`encrypted:${key}:iv`, iv),
      ]);

      this.storageProvider.put<ArrayBuffer>(
        `encrypted:${key}`,
        await crypto.subtle.encrypt({name: 'AES-GCM', iv}, aesKey, encoded),
      );
    } catch (error) {
      alert(error);
    }
  }

  async delete(key: string): Promise<void> {
    if (!Capacitor.isNativePlatform()) return;

    await Promise.all([
      SecureStoragePlugin.remove({key: `stapps:key:${key}`}),
      this.storageProvider.delete(`encrypted:${key}:iv`),
      this.storageProvider.delete(`encrypted:${key}`),
    ]);
  }
}
