import type { CaptchaPolicyOptions, CaptchaRiskResult, CaptchaRiskSignals } from "./types.ts";

export function evaluateCaptchaRisk(
  signals: CaptchaRiskSignals,
  threshold = 50,
): CaptchaRiskResult {
  let score = Math.max(0, Math.min(100, signals.customScore ?? 0));
  const reasons: string[] = [];
  const add = (points: number, reason: string): void => {
    score = Math.min(100, score + points);
    reasons.push(reason);
  };
  if ((signals.failedAttempts ?? 0) > 0)
    add(Math.min(35, (signals.failedAttempts ?? 0) * 10), "failed-attempts");
  if ((signals.requestsInWindow ?? 0) > 20)
    add(Math.min(35, ((signals.requestsInWindow ?? 0) - 20) * 2), "request-rate");
  if (signals.completionMs !== undefined && signals.completionMs < 700) add(25, "too-fast");
  if (signals.missingBrowserSignals) add(20, "missing-browser-signals");
  if (signals.suspiciousHeaders) add(20, "suspicious-headers");
  if (signals.tokenReuse) add(70, "token-reuse");
  if (signals.knownBadIp) add(60, "known-bad-ip");
  return { score, challenge: score >= threshold, reasons };
}

export function shouldRequireCaptcha(
  action: string,
  options: CaptchaPolicyOptions = {},
  signals: CaptchaRiskSignals = {},
): CaptchaRiskResult {
  if (options.neverForActions?.includes(action) || options.mode === "never") {
    return { score: 0, challenge: false, reasons: ["policy-never"] };
  }
  if (options.alwaysForActions?.includes(action) || options.mode === "always") {
    return { score: 100, challenge: true, reasons: ["policy-always"] };
  }
  if ((options.mode ?? "adaptive") === "session") {
    return { score: 100, challenge: true, reasons: ["session-unverified"] };
  }
  return evaluateCaptchaRisk(signals, options.threshold ?? 50);
}

export interface CaptchaSessionGrant {
  action: string;
  routeGroup?: string;
  expiresAt: number;
  provider: string;
}

export function validCaptchaGrant(
  grants: CaptchaSessionGrant[] | undefined,
  action: string,
  now: number,
  routeGroup?: string,
): CaptchaSessionGrant | undefined {
  return grants?.find(
    (grant) =>
      grant.expiresAt > now &&
      (grant.action === action || grant.action === "*") &&
      (!routeGroup || !grant.routeGroup || grant.routeGroup === routeGroup),
  );
}
