const STORAGE_PREFIX = 'lendry-e2e-private:';
const ALGORITHM = { name: 'ECDH', namedCurve: 'P-256' } as const;
const AES = { name: 'AES-GCM', length: 256 } as const;

interface StoredKeyMaterial {
  privateJwk: JsonWebKey;
  publicKey: string;
}

function toBase64(buffer: ArrayBuffer) {
  const bytes = new Uint8Array(buffer);
  let binary = '';
  bytes.forEach((byte) => {
    binary += String.fromCharCode(byte);
  });
  return btoa(binary);
}

function fromBase64(value: string) {
  const binary = atob(value);
  const bytes = new Uint8Array(binary.length);
  for (let index = 0; index < binary.length; index += 1) {
    bytes[index] = binary.charCodeAt(index);
  }
  return bytes.buffer;
}

async function exportPublicKey(key: CryptoKey) {
  const raw = await crypto.subtle.exportKey('spki', key);
  return toBase64(raw);
}

async function importPublicKey(base64: string) {
  return crypto.subtle.importKey('spki', fromBase64(base64), ALGORITHM, true, []);
}

async function loadStoredKeys(userId: string) {
  const stored = localStorage.getItem(`${STORAGE_PREFIX}${userId}`);
  if (!stored) return null;
  const parsed = JSON.parse(stored) as StoredKeyMaterial;
  const privateKey = await crypto.subtle.importKey('jwk', parsed.privateJwk, ALGORITHM, true, ['deriveKey']);
  return { privateKey, publicKey: parsed.publicKey };
}

async function saveStoredKeys(userId: string, privateKey: CryptoKey, publicKey: string) {
  const privateJwk = await crypto.subtle.exportKey('jwk', privateKey);
  localStorage.setItem(
    `${STORAGE_PREFIX}${userId}`,
    JSON.stringify({ privateJwk, publicKey } satisfies StoredKeyMaterial)
  );
}

export async function ensureE2EKeyPair(userId: string) {
  const existing = await loadStoredKeys(userId);
  if (existing) return existing;

  const keyPair = await crypto.subtle.generateKey(ALGORITHM, true, ['deriveKey']);
  const publicKey = await exportPublicKey(keyPair.publicKey);
  await saveStoredKeys(userId, keyPair.privateKey, publicKey);
  return { privateKey: keyPair.privateKey, publicKey };
}

async function deriveAesKey(privateKey: CryptoKey, peerPublicKeyBase64: string) {
  const peerPublicKey = await importPublicKey(peerPublicKeyBase64);
  return crypto.subtle.deriveKey({ name: 'ECDH', public: peerPublicKey }, privateKey, AES, false, ['encrypt', 'decrypt']);
}

export async function encryptDirectMessage(userId: string, peerPublicKeyBase64: string, plaintext: string) {
  const keys = await ensureE2EKeyPair(userId);
  const aesKey = await deriveAesKey(keys.privateKey, peerPublicKeyBase64);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encoded = new TextEncoder().encode(plaintext);
  const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, aesKey, encoded);
  return JSON.stringify({
    v: 1,
    iv: toBase64(iv.buffer),
    ciphertext: toBase64(ciphertext)
  });
}

export async function decryptDirectMessage(userId: string, peerPublicKeyBase64: string, payload: string) {
  const keys = await loadStoredKeys(userId);
  if (!keys) {
    return '🔒 Не удалось расшифровать сообщение';
  }
  try {
    const parsed = JSON.parse(payload) as { iv?: string; ciphertext?: string };
    if (!parsed.iv || !parsed.ciphertext) {
      return payload;
    }
    const aesKey = await deriveAesKey(keys.privateKey, peerPublicKeyBase64);
    const iv = new Uint8Array(fromBase64(parsed.iv));
    const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, aesKey, fromBase64(parsed.ciphertext));
    return new TextDecoder().decode(decrypted);
  } catch {
    return '🔒 Не удалось расшифровать сообщение';
  }
}

export async function encryptBinary(userId: string, peerPublicKeyBase64: string, data: ArrayBuffer) {
  const keys = await ensureE2EKeyPair(userId);
  const aesKey = await deriveAesKey(keys.privateKey, peerPublicKeyBase64);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, aesKey, data);
  const packed = new Uint8Array(iv.byteLength + ciphertext.byteLength);
  packed.set(iv, 0);
  packed.set(new Uint8Array(ciphertext), iv.byteLength);
  return packed.buffer;
}

export async function decryptBinary(userId: string, peerPublicKeyBase64: string, packed: ArrayBuffer) {
  const keys = await loadStoredKeys(userId);
  if (!keys) {
    throw new Error('Локальный E2E-ключ не найден');
  }
  const bytes = new Uint8Array(packed);
  const iv = bytes.slice(0, 12);
  const ciphertext = bytes.slice(12);
  const aesKey = await deriveAesKey(keys.privateKey, peerPublicKeyBase64);
  return crypto.subtle.decrypt({ name: 'AES-GCM', iv }, aesKey, ciphertext);
}
