'use client';

/**
 * usePrivacy — session-scoped passphrase privacy hook.
 *
 * All crypto runs in a Web Worker (RD-1). Idle timeout is 15 min fixed (RD-2).
 * Audit events go via POST /api/privacy/verify (RD-3 — no public audit route).
 */

import { useCallback, useEffect, useRef, useState } from 'react';
import { PRIVACY_IDLE_TIMEOUT_MS, PRIVACY_PBKDF2_ITERATIONS } from '@/lib/privacy-defaults.js';

// sessionStorage key names
const SS_UNLOCKED = 'madarx-privacy-unlocked';
const SS_UNLOCK_AT = 'madarx-privacy-unlock-at';
const SS_FAIL_COUNT = 'madarx-privacy-fail-count';
const SS_FAIL_WINDOW = 'madarx-privacy-fail-window-start';
const SS_LOCKOUT = 'madarx-privacy-lockout-until';

const MAX_FAILURES = 5;
const FAIL_WINDOW_MS = 5 * 60 * 1000;
const LOCKOUT_DURATION_MS = 15 * 60 * 1000;
const IDLE_POLL_MS = 60 * 1000;

// ---------------------------------------------------------------------------
// sessionStorage helpers — safe-fallback on kiosk/private modes
// ---------------------------------------------------------------------------

function ssGet(key) {
  try { return window.sessionStorage.getItem(key); } catch { return null; }
}
function ssSet(key, val) {
  try { window.sessionStorage.setItem(key, String(val)); } catch {}
}
function ssRemove(key) {
  try { window.sessionStorage.removeItem(key); } catch {}
}

// ---------------------------------------------------------------------------
// Worker message dispatcher — wraps postMessage in a Promise
// ---------------------------------------------------------------------------

let msgId = 0;
function dispatchWorker(worker, payload) {
  return new Promise((resolve, reject) => {
    const id = (msgId += 1);
    const handler = (e) => {
      if (e.data?.id !== id) return;
      worker.removeEventListener('message', handler);
      if (e.data.ok === false && e.data.error) {
        reject(new Error(e.data.error));
      } else {
        resolve(e.data);
      }
    };
    worker.addEventListener('message', handler);
    worker.postMessage({ id, ...payload });
  });
}

// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------

export function usePrivacy({ projectId } = {}) {
  const [config, setConfig] = useState(null);   // null = loading, { configured, ... } otherwise
  const [unlocked, setUnlocked] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);
  const [failCount, setFailCount] = useState(0);
  const [lockoutUntil, setLockoutUntil] = useState(0);

  const workerRef = useRef(null);
  const lastActivityRef = useRef(Date.now());

  // ---- Hydrate from sessionStorage -----------------------------------------
  const hydrateSession = useCallback(() => {
    const isUnlocked = ssGet(SS_UNLOCKED) === '1';
    const unlockAt = Number(ssGet(SS_UNLOCK_AT) || 0);
    // Only honour session unlock if both keys are present
    setUnlocked(isUnlocked && unlockAt > 0);

    const fc = Number(ssGet(SS_FAIL_COUNT) || 0);
    const lu = Number(ssGet(SS_LOCKOUT) || 0);
    setFailCount(fc);
    setLockoutUntil(lu > Date.now() ? lu : 0);
  }, []);

  // ---- Fetch config from server --------------------------------------------
  const fetchConfig = useCallback(async () => {
    try {
      const res = await fetch('/api/privacy/config');
      const data = await res.json();
      setConfig(data);
      return data;
    } catch (err) {
      setConfig({ configured: false });
      console.error('[usePrivacy] fetchConfig error:', err);
      return { configured: false };
    }
  }, []);

  // ---- Worker init ----------------------------------------------------------
  useEffect(() => {
    if (typeof window === 'undefined') return;
    // Create worker once
    workerRef.current = new Worker('/privacy.worker.js');
    hydrateSession();
    fetchConfig();

    return () => {
      workerRef.current?.terminate();
      workerRef.current = null;
    };
  }, [fetchConfig, hydrateSession]);

  // ---- Listen for privacy events from other components ---------------------
  useEffect(() => {
    const onConfigured = () => fetchConfig();
    const onUnlocked = () => {
      setUnlocked(true);
      fetchConfig();
    };
    const onLocked = () => {
      setUnlocked(false);
    };
    window.addEventListener('privacy:configured', onConfigured);
    window.addEventListener('privacy:unlocked', onUnlocked);
    window.addEventListener('privacy:locked', onLocked);
    return () => {
      window.removeEventListener('privacy:configured', onConfigured);
      window.removeEventListener('privacy:unlocked', onUnlocked);
      window.removeEventListener('privacy:locked', onLocked);
    };
  }, [fetchConfig]);

  // ---- Idle timeout --------------------------------------------------------
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const resetActivity = () => { lastActivityRef.current = Date.now(); };
    window.addEventListener('mousemove', resetActivity, { passive: true });
    window.addEventListener('keydown', resetActivity, { passive: true });
    window.addEventListener('pointerdown', resetActivity, { passive: true });

    const interval = setInterval(() => {
      if (!unlocked) return;
      if (Date.now() - lastActivityRef.current > PRIVACY_IDLE_TIMEOUT_MS) {
        ssRemove(SS_UNLOCKED);
        ssRemove(SS_UNLOCK_AT);
        setUnlocked(false);
      }
    }, IDLE_POLL_MS);

    return () => {
      window.removeEventListener('mousemove', resetActivity);
      window.removeEventListener('keydown', resetActivity);
      window.removeEventListener('pointerdown', resetActivity);
      clearInterval(interval);
    };
  }, [unlocked]);

  // ---- Lock ----------------------------------------------------------------
  const lock = useCallback(() => {
    ssRemove(SS_UNLOCKED);
    ssRemove(SS_UNLOCK_AT);
    setUnlocked(false);
    try { window.dispatchEvent(new CustomEvent('privacy:locked')); } catch {}
  }, []);

  // ---- Unlock --------------------------------------------------------------
  const unlock = useCallback(async (passphrase) => {
    if (!config?.configured) return { ok: false, error: 'No passphrase configured.' };
    const worker = workerRef.current;
    if (!worker) return { ok: false, error: 'Worker not ready.' };

    // Check lockout
    const lu = Number(ssGet(SS_LOCKOUT) || 0);
    if (lu > Date.now()) {
      return { ok: false, error: 'lockout', lockoutUntil: lu };
    }

    setBusy(true);
    setError(null);
    try {
      const result = await dispatchWorker(worker, {
        op: 'verify',
        passphrase,
        salt: config.salt_b64,
        iter: config.iterations,
        mac: config.hmac_b64,
      });

      if (result.match) {
        // Success
        ssSet(SS_UNLOCKED, '1');
        ssSet(SS_UNLOCK_AT, String(Date.now()));
        ssRemove(SS_FAIL_COUNT);
        ssRemove(SS_FAIL_WINDOW);
        ssRemove(SS_LOCKOUT);
        setUnlocked(true);
        setFailCount(0);
        setLockoutUntil(0);

        // Audit (best-effort, per RD-3 via /api/privacy/verify)
        fetch('/api/privacy/verify', {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({ success: true, project_id: projectId || null }),
        }).catch(() => {});

        return { ok: true };
      }

      // Failure
      const currentCount = Number(ssGet(SS_FAIL_COUNT) || 0) + 1;
      const windowStart = Number(ssGet(SS_FAIL_WINDOW) || Date.now());
      ssSet(SS_FAIL_COUNT, String(currentCount));
      ssSet(SS_FAIL_WINDOW, String(windowStart));
      setFailCount(currentCount);

      let newLockout = 0;
      if (currentCount >= MAX_FAILURES && Date.now() - windowStart < FAIL_WINDOW_MS) {
        newLockout = Date.now() + LOCKOUT_DURATION_MS;
        ssSet(SS_LOCKOUT, String(newLockout));
        setLockoutUntil(newLockout);

        fetch('/api/privacy/verify', {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({
            success: false,
            attempt_count: currentCount,
            lockout_until: new Date(newLockout).toISOString(),
            project_id: projectId || null,
          }),
        }).catch(() => {});

        return { ok: false, error: 'lockout', lockoutUntil: newLockout };
      }

      // Reset window if it expired
      if (Date.now() - windowStart >= FAIL_WINDOW_MS) {
        ssSet(SS_FAIL_COUNT, '1');
        ssSet(SS_FAIL_WINDOW, String(Date.now()));
        setFailCount(1);
      }

      fetch('/api/privacy/verify', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          success: false,
          attempt_count: currentCount,
          project_id: projectId || null,
        }),
      }).catch(() => {});

      return { ok: false, error: 'wrong_passphrase', failCount: currentCount };
    } catch (err) {
      const msg = err?.message || 'Crypto error.';
      setError(msg);
      return { ok: false, error: msg };
    } finally {
      setBusy(false);
    }
  }, [config, projectId]);

  // ---- Setup passphrase ----------------------------------------------------
  const setupPassphrase = useCallback(async (passphrase) => {
    const worker = workerRef.current;
    if (!worker) return { ok: false, error: 'Worker not ready.' };

    setBusy(true);
    setError(null);
    try {
      const result = await dispatchWorker(worker, {
        op: 'setup',
        passphrase,
        iter: PRIVACY_PBKDF2_ITERATIONS,
      });

      if (!result.ok) throw new Error(result.error || 'Worker failed.');

      const is_change = Boolean(config?.configured);

      const res = await fetch('/api/privacy/config', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          kdf: 'PBKDF2-SHA256',
          iterations: PRIVACY_PBKDF2_ITERATIONS,
          salt_b64: result.salt_b64,
          hmac_b64: result.hmac_b64,
          idle_timeout_ms: PRIVACY_IDLE_TIMEOUT_MS,
          is_change,
        }),
      });
      const data = await res.json();
      if (!data.ok) throw new Error(data.error || 'Server rejected config.');

      // Write session unlock keys
      ssSet(SS_UNLOCKED, '1');
      ssSet(SS_UNLOCK_AT, String(Date.now()));
      setUnlocked(true);

      // Refresh config
      await fetchConfig();

      // Notify other listeners
      window.dispatchEvent(new CustomEvent('privacy:configured'));

      return { ok: true };
    } catch (err) {
      const msg = err?.message || 'Setup failed.';
      setError(msg);
      return { ok: false, error: msg };
    } finally {
      setBusy(false);
    }
  }, [config, fetchConfig]);

  // ---- Reset passphrase ----------------------------------------------------
  const resetPassphrase = useCallback(async () => {
    setBusy(true);
    setError(null);
    try {
      const res = await fetch('/api/privacy/config', { method: 'DELETE' });
      const data = await res.json();
      if (!data.ok) throw new Error(data.error || 'Reset failed.');

      // Clear session state
      lock();
      ssRemove(SS_FAIL_COUNT);
      ssRemove(SS_FAIL_WINDOW);
      ssRemove(SS_LOCKOUT);
      setFailCount(0);
      setLockoutUntil(0);

      // Refresh config
      await fetchConfig();

      return { ok: true, artifacts_cleared: data.artifacts_cleared };
    } catch (err) {
      const msg = err?.message || 'Reset failed.';
      setError(msg);
      return { ok: false, error: msg };
    } finally {
      setBusy(false);
    }
  }, [lock, fetchConfig]);

  // ---- Derived values ------------------------------------------------------
  const configured = Boolean(config?.configured);

  return {
    config,
    configured,
    unlocked,
    busy,
    error,
    failCount,
    lockoutUntil,
    lock,
    unlock,
    setupPassphrase,
    resetPassphrase,
    refetchConfig: fetchConfig,
  };
}
