/**
 * ============================================
 * CAPTCHA — SERVER-SIDE LOGIC  (server-only)
 * ============================================
 * Verifies whichever CAPTCHAs the merchant activated in Shopware admin
 * (Settings → Basic information → CAPTCHA). Mirrors the default storefront:
 * Honeypot, Basic (image) captcha, Google reCAPTCHA v2 and v3 — and ALL active
 * ones must pass.
 *
 * MUST NOT be imported from a client component — it reads reCAPTCHA secret keys
 * via the Admin API. Client code talks to the /api/captcha/* route handlers,
 * which expose only a secret-free projection (getPublicCaptchaConfig) and a
 * verify gate (verifyCaptcha).
 */
import crypto from 'crypto';
import { getActiveCaptchas } from './shopware-admin';
import type { CaptchaPayload, PublicCaptchaConfig } from './captcha-types';

const SECRET = process.env.CAPTCHA_SECRET || 'sw-headless-captcha-secret-key';

/** HMAC-sign the canonical Basic-captcha answer so we never store server state. */
export function signBasicAnswer(answer: string): string {
  return crypto
    .createHmac('sha256', SECRET)
    .update(answer.toLowerCase().trim())
    .digest('hex');
}

/** Browser-safe projection of the active CAPTCHA config (no secret keys). */
export async function getPublicCaptchaConfig(): Promise<PublicCaptchaConfig> {
  const c = await getActiveCaptchas();
  return {
    honeypot: c.honeypot,
    basicCaptcha: c.basicCaptcha,
    googleReCaptchaV2: c.googleReCaptchaV2
      ? { siteKey: c.googleReCaptchaV2.siteKey, invisible: c.googleReCaptchaV2.invisible }
      : null,
    googleReCaptchaV3: c.googleReCaptchaV3
      ? { siteKey: c.googleReCaptchaV3.siteKey }
      : null,
  };
}

interface GoogleVerifyResult {
  success: boolean;
  score?: number;
}

/** Verify a Google reCAPTCHA token against Google's siteverify endpoint. */
async function verifyGoogleToken(
  secret: string,
  token: string,
  remoteIp?: string
): Promise<GoogleVerifyResult> {
  if (!secret || !token) return { success: false };
  try {
    const params = new URLSearchParams({ secret, response: token });
    if (remoteIp) params.set('remoteip', remoteIp);
    const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: params.toString(),
      cache: 'no-store',
    });
    if (!res.ok) return { success: false };
    const data: { success?: boolean; score?: number } = await res.json();
    return {
      success: Boolean(data.success),
      score: typeof data.score === 'number' ? data.score : undefined,
    };
  } catch {
    return { success: false };
  }
}

export interface CaptchaVerifyResult {
  valid: boolean;
  error?: string;
}

/**
 * Verify the submitted CAPTCHA payload against every active CAPTCHA. Returns
 * { valid: true } when no CAPTCHA is active or all active ones pass.
 */
export async function verifyCaptcha(
  payload: CaptchaPayload,
  remoteIp?: string
): Promise<CaptchaVerifyResult> {
  const c = await getActiveCaptchas();

  // Honeypot: the hidden trap field must be empty.
  if (c.honeypot && String(payload.honeypot ?? '').trim() !== '') {
    return { valid: false, error: 'Submission rejected.' };
  }

  // Basic (image) captcha: HMAC-signed math challenge.
  if (c.basicCaptcha) {
    const answer = payload.basicCaptchaAnswer;
    const token = payload.basicCaptchaToken;
    if (!answer || !token) {
      return { valid: false, error: 'Please complete the captcha.' };
    }
    const expected = signBasicAnswer(String(parseInt(answer, 10)));
    if (expected !== token) {
      return { valid: false, error: 'Incorrect captcha answer. Please try again.' };
    }
  }

  // Google reCAPTCHA v2 (checkbox / invisible).
  if (c.googleReCaptchaV2) {
    const r = await verifyGoogleToken(
      c.googleReCaptchaV2.secretKey,
      payload.recaptchaV2Token ?? '',
      remoteIp
    );
    if (!r.success) {
      return { valid: false, error: 'reCAPTCHA verification failed. Please try again.' };
    }
  }

  // Google reCAPTCHA v3 (score-based, no user interaction).
  if (c.googleReCaptchaV3) {
    const r = await verifyGoogleToken(
      c.googleReCaptchaV3.secretKey,
      payload.recaptchaV3Token ?? '',
      remoteIp
    );
    const threshold = c.googleReCaptchaV3.thresholdScore;
    if (!r.success || (typeof r.score === 'number' && r.score < threshold)) {
      return { valid: false, error: 'reCAPTCHA verification failed. Please try again.' };
    }
  }

  return { valid: true };
}

/** Pull only the request-IP for reCAPTCHA's optional remoteip parameter. */
export function clientIpFromHeaders(headers: Headers): string | undefined {
  const fwd = headers.get('x-forwarded-for');
  if (fwd) return fwd.split(',')[0]?.trim() || undefined;
  return headers.get('x-real-ip') || undefined;
}
