const PASSKEY_STORAGE_KEY = "nebula.passkey.v1";
const PASSKEY_VERSION = 1;
const PASSKEY_LENGTH = 6;

const clamp = (value, min, max) => Math.max(min, Math.min(max, value));

const FALLBACK_CONFIG = {
  enabled: true,
  length: PASSKEY_LENGTH,
  requireConfirm: false,
  keyboardSupport: true,
  maxAttempts: 5,
  cooldownSeconds: 30,
  animationSpeed: "normal",
  highContrast: false,
  hash: "",
  salt: "",
};

const arrayToHex = (bytes) =>
  Array.from(bytes)
    .map((value) => value.toString(16).padStart(2, "0"))
    .join("");

const createSalt = () => {
  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);
  return arrayToHex(bytes);
};

const normalizeSequence = (sequence) => sequence.join("|");

export const hashPasskeySequence = async (sequence, salt) => {
  const source = `${salt}::${normalizeSequence(sequence)}`;
  const encoded = new TextEncoder().encode(source);
  const digest = await crypto.subtle.digest("SHA-256", encoded);
  return arrayToHex(new Uint8Array(digest));
};

export const loadPasskeyConfig = () => {
  try {
    const raw = window.localStorage.getItem(PASSKEY_STORAGE_KEY);
    if (!raw) {
      return {
        ...FALLBACK_CONFIG,
      };
    }

    const parsed = JSON.parse(raw);
    return {
      ...FALLBACK_CONFIG,
      ...parsed,
      length: PASSKEY_LENGTH,
      maxAttempts: clamp(Number(parsed.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10),
      cooldownSeconds: clamp(Number(parsed.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120),
      requireConfirm: Boolean(parsed.requireConfirm ?? FALLBACK_CONFIG.requireConfirm),
      keyboardSupport: Boolean(parsed.keyboardSupport ?? FALLBACK_CONFIG.keyboardSupport),
      enabled: Boolean(parsed.enabled ?? FALLBACK_CONFIG.enabled),
      highContrast: Boolean(parsed.highContrast ?? FALLBACK_CONFIG.highContrast),
      animationSpeed: ["slow", "normal", "fast"].includes(parsed.animationSpeed)
        ? parsed.animationSpeed
        : FALLBACK_CONFIG.animationSpeed,
      hash: typeof parsed.hash === "string" ? parsed.hash : "",
      salt: typeof parsed.salt === "string" ? parsed.salt : "",
      version: PASSKEY_VERSION,
    };
  } catch (_error) {
    return {
      ...FALLBACK_CONFIG,
      version: PASSKEY_VERSION,
    };
  }
};

export const savePasskeyConfig = (config) => {
  const safeConfig = {
    ...FALLBACK_CONFIG,
    ...config,
    version: PASSKEY_VERSION,
    length: PASSKEY_LENGTH,
    maxAttempts: clamp(Number(config.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10),
    cooldownSeconds: clamp(Number(config.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120),
  };

  window.localStorage.setItem(PASSKEY_STORAGE_KEY, JSON.stringify(safeConfig));
  return safeConfig;
};

export const createPasskeyController = () => {
  const config = loadPasskeyConfig();
  let failedAttempts = 0;
  let lockoutUntil = 0;

  const persist = () => savePasskeyConfig(config);

  const inLockout = () => Date.now() < lockoutUntil;

  const getLockoutRemainingMs = () => Math.max(0, lockoutUntil - Date.now());

  const verifySequence = async (sequence) => {
    if (!config.enabled) {
      return { ok: true, reason: "disabled" };
    }

    if (inLockout()) {
      return {
        ok: false,
        reason: "lockout",
        lockoutRemainingMs: getLockoutRemainingMs(),
      };
    }

    if (!config.hash || !config.salt) {
      return { ok: false, reason: "setup-required" };
    }

    const hash = await hashPasskeySequence(sequence, config.salt);
    if (hash === config.hash) {
      failedAttempts = 0;
      return { ok: true, reason: "match" };
    }

    failedAttempts += 1;
    const attemptsLeft = Math.max(0, config.maxAttempts - failedAttempts);
    if (attemptsLeft <= 0) {
      lockoutUntil = Date.now() + config.cooldownSeconds * 1000;
      failedAttempts = 0;
      return {
        ok: false,
        reason: "lockout",
        lockoutRemainingMs: getLockoutRemainingMs(),
      };
    }

    return {
      ok: false,
      reason: "mismatch",
      attemptsLeft,
    };
  };

  const setSequence = async (sequence) => {
    const nextSalt = createSalt();
    const nextHash = await hashPasskeySequence(sequence, nextSalt);
    config.salt = nextSalt;
    config.hash = nextHash;
    failedAttempts = 0;
    lockoutUntil = 0;
    persist();
  };

  const updateConfig = (partial) => {
    Object.assign(config, partial);
    config.length = PASSKEY_LENGTH;
    config.maxAttempts = clamp(Number(config.maxAttempts), 1, 10);
    config.cooldownSeconds = clamp(Number(config.cooldownSeconds), 5, 120);
    persist();
    return { ...config };
  };

  const resetSequence = () => {
    config.hash = "";
    config.salt = "";
    config.length = PASSKEY_LENGTH;
    failedAttempts = 0;
    lockoutUntil = 0;
    persist();
  };

  const getConfig = () => ({ ...config });

  return {
    getConfig,
    updateConfig,
    verifySequence,
    setSequence,
    resetSequence,
    inLockout,
    getLockoutRemainingMs,
    hasPasskey: () => Boolean(config.hash && config.salt),
  };
};

export const PASSKEY_ACTIONS = ["up", "down", "left", "right", "l1", "r1", "l2", "r2"];
