import {
  base64ToBytes,
  bytesToBase64,
  deriveOpaqueArgon2Hash,
  exportEd25519PublicKeyB64,
  exportPkcs8B64,
  generateEd25519Pair,
  sha256Bytes,
  utf8Bytes,
} from './crypto-utils.js';
import {
  edwardsToMontgomeryPriv,
  edwardsToMontgomeryPub,
  x25519,
} from 'https://esm.sh/@noble/curves@1.5.0/ed25519';

const PAIRING_HASH_SUFFIX = 'esp.pairing.password';
const PAIRING_ENVELOPE_PREFIX = 'shine-esp-pairing-v1:';
const ED25519_PKCS8_PREFIX = new Uint8Array([
  0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
]);

function getCryptoApi() {
  const api = globalThis.crypto;
  if (!api?.subtle || typeof api.getRandomValues !== 'function') {
    throw new Error('Криптография браузера недоступна. Откройте приложение через HTTPS или localhost.');
  }
  return api;
}

function bytesToBase64Url(bytes) {
  return bytesToBase64(bytes).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}

function base64UrlToBytes(value) {
  const normalized = String(value || '').trim().replace(/-/g, '+').replace(/_/g, '/');
  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
  return base64ToBytes(padded);
}

function extractSeedFromPkcs8(pkcs8B64) {
  const raw = base64ToBytes(pkcs8B64);
  if (raw.length !== ED25519_PKCS8_PREFIX.length + 32) {
    throw new Error('Некорректный приватный Ed25519 ключ');
  }
  for (let i = 0; i < ED25519_PKCS8_PREFIX.length; i += 1) {
    if (raw[i] !== ED25519_PKCS8_PREFIX[i]) {
      throw new Error('Неподдерживаемый формат приватного Ed25519 ключа');
    }
  }
  return raw.slice(ED25519_PKCS8_PREFIX.length);
}

function extractSessionPublicKeyB64(sessionKey) {
  const raw = String(sessionKey || '').trim();
  if (!raw.startsWith('ed25519/')) {
    throw new Error('Неподдерживаемый requesterSessionKey');
  }
  const publicKeyB64 = raw.slice('ed25519/'.length).trim();
  if (!publicKeyB64) {
    throw new Error('Пустой requesterSessionKey');
  }
  return publicKeyB64;
}

async function importAesKeyFromSharedSecret(sharedSecretBytes) {
  const digest = await sha256Bytes(sharedSecretBytes);
  return getCryptoApi().subtle.importKey('raw', digest, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
}

function normalizeKeys(keys = {}) {
  return {
    deviceKey: String(keys?.deviceKey || '').trim(),
    blockchainKey: String(keys?.blockchainKey || '').trim(),
    rootKey: String(keys?.rootKey || '').trim(),
  };
}

export function detectPairingPayloadType(keys = {}) {
  const normalized = normalizeKeys(keys);
  if (normalized.rootKey) return 3;
  if (normalized.blockchainKey) return 2;
  return 1;
}

export async function deriveEspPairingPasswordHash(login, password) {
  return deriveOpaqueArgon2Hash(password, {
    login,
    suffix: PAIRING_HASH_SUFFIX,
  });
}

export async function createRequesterPairingMaterial() {
  const sessionPair = await generateEd25519Pair();
  const sessionPublicB64 = await exportEd25519PublicKeyB64(sessionPair.publicKey);
  return {
    sessionKey: `ed25519/${sessionPublicB64}`,
    sessionPrivPkcs8: await exportPkcs8B64(sessionPair.privateKey),
  };
}

export async function encryptPairingPayloadForRequester(requesterSessionKey, payload) {
  const requesterPublicB64 = extractSessionPublicKeyB64(requesterSessionKey);
  const requesterMontPub = edwardsToMontgomeryPub(base64ToBytes(requesterPublicB64));
  const ephemeralPriv = x25519.utils.randomPrivateKey();
  const ephemeralPub = x25519.getPublicKey(ephemeralPriv);
  const sharedSecret = x25519.getSharedSecret(ephemeralPriv, requesterMontPub);
  const aesKey = await importAesKeyFromSharedSecret(sharedSecret);
  const iv = getCryptoApi().getRandomValues(new Uint8Array(12));
  const plainBytes = utf8Bytes(JSON.stringify(payload));
  const cipher = await getCryptoApi().subtle.encrypt({ name: 'AES-GCM', iv }, aesKey, plainBytes);
  const envelope = {
    v: 1,
    alg: 'x25519-aes256-gcm',
    ephPubB64: bytesToBase64(ephemeralPub),
    ivB64: bytesToBase64(iv),
    cipherB64: bytesToBase64(new Uint8Array(cipher)),
    createdAtMs: Date.now(),
  };
  return `${PAIRING_ENVELOPE_PREFIX}${bytesToBase64Url(utf8Bytes(JSON.stringify(envelope)))}`;
}

export async function decryptPairingPayloadFromEnvelope(encryptedPayload, requesterPairingMaterial) {
  const raw = String(encryptedPayload || '').trim();
  if (!raw.startsWith(PAIRING_ENVELOPE_PREFIX)) {
    throw new Error('Неподдерживаемый формат pairing payload');
  }
  const jsonBytes = base64UrlToBytes(raw.slice(PAIRING_ENVELOPE_PREFIX.length));
  const envelope = JSON.parse(new TextDecoder().decode(jsonBytes));
  if (Number(envelope?.v) !== 1 || String(envelope?.alg || '') !== 'x25519-aes256-gcm') {
    throw new Error('Неподдерживаемая версия pairing payload');
  }

  const requesterSeed = extractSeedFromPkcs8(String(requesterPairingMaterial?.sessionPrivPkcs8 || ''));
  const requesterMontPriv = edwardsToMontgomeryPriv(requesterSeed);
  const sharedSecret = x25519.getSharedSecret(requesterMontPriv, base64ToBytes(String(envelope?.ephPubB64 || '')));
  const aesKey = await importAesKeyFromSharedSecret(sharedSecret);
  const plain = await getCryptoApi().subtle.decrypt(
    { name: 'AES-GCM', iv: base64ToBytes(String(envelope?.ivB64 || '')) },
    aesKey,
    base64ToBytes(String(envelope?.cipherB64 || '')),
  );
  const payload = JSON.parse(new TextDecoder().decode(plain));
  return {
    ...payload,
    keys: normalizeKeys(payload?.keys),
  };
}

export function buildSecretsPayload({ login, keys, mode }) {
  return {
    v: 1,
    type: 'shine-esp-pairing-transfer',
    login: String(login || '').trim(),
    mode: String(mode || 'device-only').trim() || 'device-only',
    keys: normalizeKeys(keys),
    payloadType: detectPairingPayloadType(keys),
    createdAtMs: Date.now(),
  };
}
