import initPrivacyCore, {
  wasm_gate_decrypt,
  wasm_gate_encrypt,
  wasm_gate_export_state,
  wasm_gate_import_state,
  wasm_release_group,
  wasm_release_identity,
  wasm_reset_all_state,
} from './privacyCoreWasm/privacy_core';
import {
  clearWorkerGateStates,
  deleteWorkerGateState,
  probeWorkerGateVaultAvailability,
  readWorkerGateState,
  writeWorkerGateState,
  type WorkerGateStateMember,
  type WorkerGateStateSnapshot,
} from './meshGateWorkerVault';

export type LocalGateComposeResult = {
  gate_id: string;
  epoch: number;
  ciphertext: string;
  nonce: string;
};

export type LocalGateDecryptResult = {
  ok: boolean;
  gate_id: string;
  epoch?: number;
  plaintext?: string;
  reply_to?: string;
  detail?: string;
  identity_scope?: string;
};

export type InlineGateCryptoSupport = {
  supported: boolean;
  reason: string;
};

type GateStateImportMapping = {
  identities: Record<string, number>;
  groups: Record<string, number>;
};

type ImportedGateState = {
  snapshot: WorkerGateStateSnapshot;
  identityHandles: number[];
  groupHandles: number[];
  activeGroupHandle: number;
  members: WorkerGateStateMember[];
};

const GATE_BUCKETS = [192, 384, 768, 1536, 3072, 6144];

let wasmReady: Promise<void> | null = null;
const gateStateCache = new Map<string, ImportedGateState>();

function normalizeGateId(gateId: string): string {
  return String(gateId || '').trim().toLowerCase();
}

function bytesToBase64(bytes: Uint8Array): string {
  return btoa(String.fromCharCode(...bytes));
}

function base64ToBytes(value: string): Uint8Array {
  const binary = atob(String(value || '').trim());
  const out = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i += 1) {
    out[i] = binary.charCodeAt(i);
  }
  return out;
}

function padGateCiphertext(raw: Uint8Array): Uint8Array {
  const prefixed = new Uint8Array(raw.length + 2);
  const len = Math.min(raw.length, 0xffff);
  prefixed[0] = (len >> 8) & 0xff;
  prefixed[1] = len & 0xff;
  prefixed.set(raw, 2);
  for (const bucket of GATE_BUCKETS) {
    if (prefixed.length <= bucket) {
      const out = new Uint8Array(bucket);
      out.set(prefixed);
      return out;
    }
  }
  const lastBucket = GATE_BUCKETS[GATE_BUCKETS.length - 1] || 6144;
  const target = Math.ceil(prefixed.length / lastBucket) * lastBucket;
  const out = new Uint8Array(target);
  out.set(prefixed);
  return out;
}

function unpadGateCiphertext(padded: Uint8Array): Uint8Array {
  if (padded.length < 2) return padded;
  const originalLen = ((padded[0] << 8) | padded[1]) >>> 0;
  if (originalLen <= 0 || originalLen + 2 > padded.length) {
    return padded;
  }
  return padded.slice(2, 2 + originalLen);
}

function encodeGateCiphertext(raw: Uint8Array): string {
  return bytesToBase64(padGateCiphertext(raw));
}

function decodeGateCiphertext(ciphertextB64: string): Uint8Array {
  return unpadGateCiphertext(base64ToBytes(ciphertextB64));
}

function encodeGatePlaintext(plaintext: string, epoch: number, replyTo: string = ''): Uint8Array {
  const normalizedReplyTo = String(replyTo || '').trim();
  return new TextEncoder().encode(
    JSON.stringify({
      m: plaintext,
      e: epoch,
      ...(normalizedReplyTo ? { r: normalizedReplyTo } : {}),
    }),
  );
}

function decodeGatePlaintext(ciphertextOpen: Uint8Array, fallbackEpoch: number): {
  plaintext: string;
  epoch: number;
  reply_to: string;
} {
  const raw = new TextDecoder().decode(ciphertextOpen);
  try {
    const parsed = JSON.parse(raw) as { m?: string; e?: number; r?: string };
    return {
      plaintext: typeof parsed?.m === 'string' ? parsed.m : raw,
      epoch: Number.isFinite(parsed?.e) ? Number(parsed.e) : fallbackEpoch,
      reply_to: typeof parsed?.r === 'string' ? parsed.r : '',
    };
  } catch {
    return { plaintext: raw, epoch: fallbackEpoch, reply_to: '' };
  }
}

function generateGateNonce(): string {
  const bytes = new Uint8Array(12);
  crypto.getRandomValues(bytes);
  return bytesToBase64(bytes);
}

function gateMemberMatchesActive(
  snapshot: WorkerGateStateSnapshot,
  member: WorkerGateStateMember,
): boolean {
  const activeScope = String(snapshot.active_identity_scope || '').trim().toLowerCase();
  if (activeScope === 'persona') {
    const activePersonaId = String(snapshot.active_persona_id || '').trim();
    return Boolean(activePersonaId) && String(member.persona_id || '').trim() === activePersonaId;
  }
  const activeNodeId = String(snapshot.active_node_id || '').trim();
  return (
    Boolean(activeNodeId) &&
    String(member.node_id || '').trim() === activeNodeId &&
    String(member.identity_scope || '').trim().toLowerCase() === 'anonymous'
  );
}

async function ensureWasm(): Promise<void> {
  if (!wasmReady) {
    wasmReady = initPrivacyCore().then(() => undefined);
  }
  await wasmReady;
}

function releaseImportedState(imported: ImportedGateState): void {
  for (const groupHandle of imported.groupHandles) {
    try {
      wasm_release_group(BigInt(groupHandle));
    } catch {
      /* ignore */
    }
  }
  for (const identityHandle of imported.identityHandles) {
    try {
      wasm_release_identity(BigInt(identityHandle));
    } catch {
      /* ignore */
    }
  }
}

function cacheImportedState(imported: ImportedGateState): void {
  const gateId = normalizeGateId(imported.snapshot.gate_id);
  const previous = gateStateCache.get(gateId);
  if (previous) {
    releaseImportedState(previous);
  }
  gateStateCache.set(gateId, imported);
}

function parseImportMapping(json: string): GateStateImportMapping {
  const parsed = JSON.parse(json) as Partial<GateStateImportMapping>;
  return {
    identities: parsed.identities || {},
    groups: parsed.groups || {},
  };
}

async function importSnapshot(snapshot: WorkerGateStateSnapshot): Promise<ImportedGateState> {
  await ensureWasm();
  const blob = base64ToBytes(snapshot.rust_state_blob_b64);
  const mapping = parseImportMapping(wasm_gate_import_state(blob));
  const remappedMembers = (snapshot.members || []).map((member) => {
    const key = String(member.group_handle);
    const mapped = Number(mapping.groups[key] || 0);
    if (!mapped) {
      throw new Error(`browser_gate_state_mapping_missing_group:${key}`);
    }
    return {
      ...member,
      group_handle: mapped,
    };
  });
  const activeMember = remappedMembers.find((member) => gateMemberMatchesActive(snapshot, member));
  if (!activeMember?.group_handle) {
    throw new Error('browser_gate_state_active_member_missing');
  }
  return {
    snapshot: {
      ...snapshot,
      gate_id: normalizeGateId(snapshot.gate_id),
      members: remappedMembers,
    },
    identityHandles: Object.values(mapping.identities || {}).map((value) => Number(value)),
    groupHandles: Array.from(
      new Set(remappedMembers.map((member) => Number(member.group_handle)).filter(Boolean)),
    ),
    activeGroupHandle: Number(activeMember.group_handle),
    members: remappedMembers,
  };
}

async function persistImportedState(imported: ImportedGateState): Promise<void> {
  await ensureWasm();
  const blob = wasm_gate_export_state(
    JSON.stringify(imported.identityHandles),
    JSON.stringify(imported.groupHandles),
  );
  const snapshot: WorkerGateStateSnapshot = {
    ...imported.snapshot,
    gate_id: normalizeGateId(imported.snapshot.gate_id),
    rust_state_blob_b64: bytesToBase64(blob),
    members: imported.members,
  };
  imported.snapshot = snapshot;
  await writeWorkerGateState(snapshot);
}

async function ensureImportedGateState(gateId: string): Promise<ImportedGateState> {
  const normalized = normalizeGateId(gateId);
  const cached = gateStateCache.get(normalized);
  if (cached) return cached;
  const snapshot = await readWorkerGateState(normalized);
  if (!snapshot) {
    throw new Error(`browser_gate_state_resync_required:${normalized}`);
  }
  const imported = await importSnapshot(snapshot);
  cacheImportedState(imported);
  return imported;
}

export async function isInlineGateCryptoSupported(): Promise<boolean> {
  const support = await probeInlineGateCryptoSupport();
  return support.supported;
}

export async function probeInlineGateCryptoSupport(): Promise<InlineGateCryptoSupport> {
  if (
    typeof window === 'undefined' ||
    typeof crypto === 'undefined'
  ) {
    return { supported: false, reason: 'browser_runtime_unavailable' };
  }
  if (!crypto?.subtle) {
    return { supported: false, reason: 'browser_gate_webcrypto_unavailable' };
  }
  if (typeof indexedDB === 'undefined') {
    return { supported: false, reason: 'browser_gate_indexeddb_unavailable' };
  }
  const vault = await probeWorkerGateVaultAvailability();
  if (!vault.ok) {
    return { supported: false, reason: vault.reason || 'browser_gate_storage_unavailable' };
  }
  try {
    await ensureWasm();
    return { supported: true, reason: '' };
  } catch (error) {
    return {
      supported: false,
      reason:
        (error instanceof Error ? error.message : String(error || '')).trim() ||
        'browser_gate_wasm_unavailable',
    };
  }
}

export async function adoptInlineGateState(snapshot: WorkerGateStateSnapshot): Promise<WorkerGateStateSnapshot> {
  const imported = await importSnapshot(snapshot);
  cacheImportedState(imported);
  await persistImportedState(imported);
  return imported.snapshot;
}

export async function composeInlineGateMessage(
  gateId: string,
  plaintext: string,
  replyTo: string = '',
): Promise<LocalGateComposeResult> {
  const imported = await ensureImportedGateState(gateId);
  const encodedPlaintext = encodeGatePlaintext(
    plaintext,
    Number(imported.snapshot.epoch || 0),
    replyTo,
  );
  const rawCiphertext = wasm_gate_encrypt(BigInt(imported.activeGroupHandle), encodedPlaintext);
  await persistImportedState(imported);
  return {
    gate_id: normalizeGateId(gateId),
    epoch: Number(imported.snapshot.epoch || 0),
    ciphertext: encodeGateCiphertext(rawCiphertext),
    nonce: generateGateNonce(),
  };
}

export async function decryptInlineGateMessages(
  messages: Array<{ gate_id: string; epoch?: number; ciphertext: string }>,
): Promise<LocalGateDecryptResult[]> {
  const results: LocalGateDecryptResult[] = [];
  for (const message of messages) {
    const gateId = normalizeGateId(message.gate_id);
    try {
      const imported = await ensureImportedGateState(gateId);
      const requestedEpoch = Number(message.epoch || 0);
      if (requestedEpoch > 0 && requestedEpoch > Number(imported.snapshot.epoch || 0)) {
        results.push({
          ok: false,
          detail: `browser_gate_state_resync_required:${gateId}`,
          gate_id: gateId,
        });
        continue;
      }
      const ciphertext = decodeGateCiphertext(message.ciphertext);
      let opened: Uint8Array | null = null;
      for (const groupHandle of imported.groupHandles) {
        try {
          opened = wasm_gate_decrypt(BigInt(groupHandle), ciphertext);
          break;
        } catch {
          /* keep trying remapped members */
        }
      }
      if (!opened) {
        results.push({
          ok: false,
          detail: 'gate_mls_decrypt_failed',
          gate_id: gateId,
        });
        continue;
      }
      const decoded = decodeGatePlaintext(opened, requestedEpoch || Number(imported.snapshot.epoch || 0));
      await persistImportedState(imported);
      results.push({
        ok: true,
        gate_id: gateId,
        epoch: decoded.epoch,
        plaintext: decoded.plaintext,
        reply_to: decoded.reply_to,
        identity_scope: 'browser_privacy_core',
      });
    } catch (error) {
      results.push({
        ok: false,
        detail: error instanceof Error ? error.message : 'browser_gate_crypto_error',
        gate_id: gateId,
      });
    }
  }
  return results;
}

export async function forgetInlineGateState(gateId?: string): Promise<void> {
  const normalized = normalizeGateId(gateId || '');
  if (!normalized) {
    for (const imported of gateStateCache.values()) {
      releaseImportedState(imported);
    }
    gateStateCache.clear();
    try {
      wasm_reset_all_state();
    } catch {
      /* ignore */
    }
    await clearWorkerGateStates();
    return;
  }
  const existing = gateStateCache.get(normalized);
  if (existing) {
    releaseImportedState(existing);
    gateStateCache.delete(normalized);
  }
  await deleteWorkerGateState(normalized);
}
