/**
 * PCA SDK - Person-Corporate-Agent Human Detection SDK
 *
 * Collects behavioral metrics to distinguish humans from bots
 * and handles step-up verification (WebAuthn, PoW) when needed.
 *
 * GDPR/Privacy compliant: No PII collected, only aggregated behavior metrics.
 */

const PCA = (() => {
  // Internal state
  let config = null;
  let sessionId = null;
  let metrics = {
    startTime: Date.now(),
    visibleTime: 0,
    lastVisibleTS: Date.now(),
    maxScrollPercent: 0,
    clickCount: 0,
    mouseMoves: 0,
    keyStrokes: 0,
    touchEvents: 0,
    mouseVelocities: [],
    scrollVelocities: [],
    clickIntervals: [],
    lastClickTime: 0,
    lastMousePos: null,
    lastMouseTime: 0,
    lastScrollPos: 0,
    lastScrollTime: 0
  };
  let currentScore = 0;
  let tickTimer = null;
  let isInitialized = false;
  let scoreCallbacks = [];

  // Generate unique session ID
  function generateSessionId() {
    return 'pca_' + Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9);
  }

  // Calculate score based on current metrics
  function evaluateScore() {
    const now = Date.now();
    const totalTime = (now - metrics.startTime) / 1000;

    // Update visible time if page is visible
    if (!document.hidden) {
      metrics.visibleTime += (now - metrics.lastVisibleTS) / 1000;
      metrics.lastVisibleTS = now;
    }

    // Heuristic 1: Dwell ratio (visible time / total time)
    let dwellRatio = totalTime > 0 ? (metrics.visibleTime / totalTime) : 0;
    if (dwellRatio > 1) dwellRatio = 1;

    // Heuristic 2: Scroll score (max scroll depth 0-1)
    let scrollScore = metrics.maxScrollPercent;
    if (scrollScore > 1) scrollScore = 1;

    // Heuristic 3: Pointer variance (mouse/touch activity)
    let pointerScore = 0;
    if (metrics.mouseMoves > 0 || metrics.touchEvents > 0) {
      pointerScore = 0.5;
      // Check for natural mouse velocity variance
      if (metrics.mouseVelocities.length > 5) {
        const variance = calculateVariance(metrics.mouseVelocities);
        if (variance > 0.1 && variance < 100.0) {
          pointerScore = 0.9; // Natural variance
        } else if (variance <= 0.1) {
          pointerScore = 0.3; // Too uniform - suspicious
        }
      }
      if (metrics.touchEvents > 0) pointerScore += 0.2;
      if (pointerScore > 1) pointerScore = 1;
    }

    // Heuristic 4: Click rate
    let clickScore = 0;
    if (metrics.clickCount > 0 && totalTime > 0) {
      const clickRate = metrics.clickCount / totalTime;
      if (clickRate > 0.05 && clickRate < 3.0) {
        clickScore = 0.8;
      } else if (clickRate >= 3.0) {
        clickScore = 0.2; // Too fast
      } else {
        clickScore = 0.4;
      }
      // Natural click intervals
      if (metrics.clickIntervals.length > 2) {
        const variance = calculateVariance(metrics.clickIntervals);
        if (variance > 0.01) clickScore += 0.2;
        if (clickScore > 1) clickScore = 1;
      }
    }

    // Weighted sum
    const w = config?.weights || { dwell_ratio: 0.30, scroll_score: 0.25, pointer_variance: 0.20, click_rate: 0.25 };
    currentScore =
      dwellRatio * (w.dwell_ratio || 0) +
      scrollScore * (w.scroll_score || 0) +
      pointerScore * (w.pointer_variance || 0) +
      clickScore * (w.click_rate || 0);

    if (currentScore > 1) currentScore = 1;
    if (currentScore < 0) currentScore = 0;

    return currentScore;
  }

  // Calculate variance of an array
  function calculateVariance(values) {
    if (values.length < 2) return 0;
    const mean = values.reduce((a, b) => a + b, 0) / values.length;
    return values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (values.length - 1);
  }

  // Send tick to backend
  async function sendTick() {
    if (!config?.tick?.endpoint) return;

    const now = Date.now();
    const totalTime = (now - metrics.startTime) / 1000;

    const payload = {
      session_id: sessionId,
      score: Number(currentScore.toFixed(3)),
      dwell_ratio: Number((metrics.visibleTime / totalTime).toFixed(3)),
      scroll_depth: Number((metrics.maxScrollPercent * 100).toFixed(1)),
      clicks: metrics.clickCount,
      mouse_moves: metrics.mouseMoves,
      key_strokes: metrics.keyStrokes,
      touch_events: metrics.touchEvents,
      mouse_velocities: metrics.mouseVelocities.slice(-20), // Last 20 values
      scroll_velocities: metrics.scrollVelocities.slice(-20),
      click_intervals: metrics.clickIntervals.slice(-10),
      ts: now
    };

    try {
      const response = await fetch(config.tick.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      });

      if (response.ok) {
        const data = await response.json();
        // Notify callbacks
        scoreCallbacks.forEach(cb => cb(data.score, data.action, data));
      }
    } catch (err) {
      console.warn('PCA: Tick transmission failed:', err);
    }
  }

  // WebAuthn step-up
  async function triggerWebAuthn() {
    if (!config?.step_up?.webauthn?.enabled || !window.PublicKeyCredential) {
      return false;
    }

    try {
      // Get challenge from server
      const challengeUrl = `${config.step_up.webauthn.challenge_endpoint}?session_id=${sessionId}`;
      const challengeResp = await fetch(challengeUrl);
      const challengeData = await challengeResp.json();

      // Convert base64url challenge to ArrayBuffer
      const challenge = base64UrlToArrayBuffer(challengeData.publicKey.challenge);

      const publicKeyRequestOptions = {
        challenge: challenge,
        timeout: challengeData.publicKey.timeout,
        userVerification: challengeData.publicKey.userVerification,
        allowCredentials: challengeData.publicKey.allowCredentials || []
      };

      // Request credential
      const credential = await navigator.credentials.get({ publicKey: publicKeyRequestOptions });

      // Send to server for verification
      const verifyResp = await fetch('/pca/v1/webauthn-verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          session_id: sessionId,
          credential: credentialToJSON(credential)
        })
      });

      const result = await verifyResp.json();
      return result.verified === true;
    } catch (e) {
      console.log('PCA: WebAuthn step-up failed:', e);
      return false;
    }
  }

  // Proof-of-Work step-up
  async function triggerPoW() {
    if (!config?.step_up?.pow?.enabled) {
      return false;
    }

    try {
      // Get challenge from server
      const challengeResp = await fetch(`/pca/v1/pow-challenge?session_id=${sessionId}`);
      const challengeData = await challengeResp.json();

      const { challenge_id, challenge, difficulty, max_time_ms } = challengeData;
      const prefix = '0'.repeat(difficulty);
      const startTime = Date.now();
      let nonce = 0;

      // Solve PoW puzzle
      while (true) {
        const input = challenge + nonce;
        const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');

        if (hashHex.startsWith(prefix)) {
          // Found solution - verify with server
          const verifyResp = await fetch('/pca/v1/pow-verify', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              session_id: sessionId,
              challenge_id: challenge_id,
              challenge: challenge,
              nonce: nonce
            })
          });

          const result = await verifyResp.json();
          return result.verified === true;
        }

        nonce++;

        // Check timeout
        if (Date.now() - startTime > max_time_ms) {
          console.warn('PCA: PoW step-up timed out');
          return false;
        }

        // Yield to prevent UI freeze (every 1000 iterations)
        if (nonce % 1000 === 0) {
          await new Promise(r => setTimeout(r, 0));
        }
      }
    } catch (e) {
      console.error('PCA: PoW step-up error:', e);
      return false;
    }
  }

  // Trigger step-up based on configured primary method
  async function triggerStepUp() {
    const methods = config?.step_up;
    let success = false;

    if (methods?.primary === 'webauthn' && methods?.webauthn?.enabled && window.PublicKeyCredential) {
      success = await triggerWebAuthn();
    }

    if (!success && methods?.pow?.enabled) {
      success = await triggerPoW();
    }

    return success;
  }

  // Helper: Convert base64url to ArrayBuffer
  function base64UrlToArrayBuffer(base64url) {
    const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
    const padding = '='.repeat((4 - base64.length % 4) % 4);
    const binary = atob(base64 + padding);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i);
    }
    return bytes.buffer;
  }

  // Helper: Convert credential to JSON-serializable object
  function credentialToJSON(credential) {
    return {
      id: credential.id,
      type: credential.type,
      rawId: arrayBufferToBase64Url(credential.rawId),
      response: {
        authenticatorData: arrayBufferToBase64Url(credential.response.authenticatorData),
        clientDataJSON: arrayBufferToBase64Url(credential.response.clientDataJSON),
        signature: arrayBufferToBase64Url(credential.response.signature)
      }
    };
  }

  // Helper: Convert ArrayBuffer to base64url
  function arrayBufferToBase64Url(buffer) {
    const bytes = new Uint8Array(buffer);
    let binary = '';
    for (let i = 0; i < bytes.length; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
  }

  // Initialize SDK
  function init(userConfig) {
    if (isInitialized) return;

    config = userConfig;
    sessionId = generateSessionId();
    isInitialized = true;

    // Visibility change listener
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        metrics.visibleTime += (Date.now() - metrics.lastVisibleTS) / 1000;
      } else {
        metrics.lastVisibleTS = Date.now();
      }
    });

    // Scroll listener
    window.addEventListener('scroll', () => {
      const doc = document.documentElement;
      const scrollTop = window.pageYOffset || doc.scrollTop;
      const viewportHeight = window.innerHeight;
      const totalHeight = doc.scrollHeight;
      const scrollPercent = totalHeight > 0 ? (scrollTop + viewportHeight) / totalHeight : 0;

      if (scrollPercent > metrics.maxScrollPercent) {
        metrics.maxScrollPercent = scrollPercent;
      }

      // Track scroll velocity
      const now = Date.now();
      if (metrics.lastScrollTime > 0) {
        const dt = (now - metrics.lastScrollTime) / 1000;
        if (dt > 0) {
          const velocity = Math.abs(scrollTop - metrics.lastScrollPos) / dt;
          metrics.scrollVelocities.push(velocity);
          if (metrics.scrollVelocities.length > 50) metrics.scrollVelocities.shift();
        }
      }
      metrics.lastScrollPos = scrollTop;
      metrics.lastScrollTime = now;
    });

    // Mouse movement listener
    document.addEventListener('mousemove', (e) => {
      metrics.mouseMoves++;

      // Track mouse velocity
      const now = Date.now();
      if (metrics.lastMousePos && metrics.lastMouseTime > 0) {
        const dt = (now - metrics.lastMouseTime) / 1000;
        if (dt > 0) {
          const dx = e.clientX - metrics.lastMousePos.x;
          const dy = e.clientY - metrics.lastMousePos.y;
          const velocity = Math.sqrt(dx * dx + dy * dy) / dt;
          metrics.mouseVelocities.push(velocity);
          if (metrics.mouseVelocities.length > 50) metrics.mouseVelocities.shift();
        }
      }
      metrics.lastMousePos = { x: e.clientX, y: e.clientY };
      metrics.lastMouseTime = now;
    });

    // Click listener
    document.addEventListener('click', () => {
      const now = Date.now();
      if (metrics.lastClickTime > 0) {
        const interval = (now - metrics.lastClickTime) / 1000;
        metrics.clickIntervals.push(interval);
        if (metrics.clickIntervals.length > 20) metrics.clickIntervals.shift();
      }
      metrics.lastClickTime = now;
      metrics.clickCount++;
    });

    // Keystroke listener (count only, no content)
    document.addEventListener('keydown', () => {
      metrics.keyStrokes++;
    });

    // Touch listener (mobile)
    document.addEventListener('touchstart', () => {
      metrics.touchEvents++;
    });

    // Start tick timer
    if (config?.tick?.interval_ms) {
      tickTimer = setInterval(() => {
        evaluateScore();
        sendTick();
      }, config.tick.interval_ms);
    }
  }

  // Public API
  return {
    init,

    getScore: () => currentScore,

    getSessionId: () => sessionId,

    triggerStepUp,

    triggerWebAuthn,

    triggerPoW,

    onScoreUpdate: function(callback) {
      scoreCallbacks.push(callback);
      // Initial score
      evaluateScore();
      callback(currentScore, currentScore >= (config?.thresholds?.score_pass || 0.7) ? 'allow' : 'challenge', null);
    },

    // Manual evaluation
    evaluate: () => {
      return {
        score: evaluateScore(),
        session_id: sessionId,
        metrics: {
          dwell_ratio: metrics.visibleTime / ((Date.now() - metrics.startTime) / 1000),
          scroll_depth: metrics.maxScrollPercent,
          clicks: metrics.clickCount,
          mouse_moves: metrics.mouseMoves
        }
      };
    },

    // Force send tick
    tick: sendTick,

    // Cleanup
    destroy: () => {
      if (tickTimer) {
        clearInterval(tickTimer);
        tickTimer = null;
      }
      isInitialized = false;
      scoreCallbacks = [];
    }
  };
})();

// Auto-initialize if config is available
if (typeof window !== 'undefined') {
  window.PCA = PCA;

  // Try to load config from ai-access.json
  fetch('/ai-access.json')
    .then(res => res.ok ? res.json() : null)
    .catch(() => null)
    .then(cfg => {
      if (cfg) {
        PCA.init(cfg);
      }
    });
}

// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
  module.exports = PCA;
}
