/**
 * Server Actions for Next.js
 * Authentication actions for login, register, password reset, etc.
 */

'use server';

import { register, login, requestPasswordReset, resetPassword, verifyUserEmail } from '../lib/auth.js';
import { validateSession, deleteSession } from '../lib/session.js';
import { verifyEmailToken, verifyResetToken, sendVerificationEmail, sendPasswordResetEmail } from '../lib/email.js';
import { fail } from '@zen/core/shared/logger';
import { cookies, headers } from 'next/headers';
import { getSessionCookieName, getPublicBaseUrl } from '@zen/core/shared/config';
import { checkRateLimit, getIpFromHeaders, formatRetryAfter } from '@zen/core/shared/rate-limit';

/**
 * Errors that are safe to surface verbatim to the client (e.g. "token expired").
 * All other errors — including library-layer and database errors — must be caught,
 * logged server-side only, and replaced with a generic message to prevent internal
 * detail disclosure.
 */
class UserFacingError extends Error {
  constructor(message) {
    super(message);
    this.name = 'UserFacingError';
  }
}

/**
 * Get the client IP from the current server action context.
 */
async function getClientIp() {
  const h = await headers();
  return getIpFromHeaders(h);
}

// Emitted at most once per process lifetime to avoid log flooding while still
// alerting operators that per-IP rate limiting is inactive for server actions.
let _rateLimitUnavailableWarned = false;

/**
 * Apply per-IP rate limiting only when a real IP address is available.
 *
 * When IP resolves to 'unknown' (no trusted proxy configured), every caller
 * shares the single bucket keyed '<action>:unknown'. A single attacker can
 * exhaust that bucket in 5 requests and impose a 30-minute denial-of-service
 * on every legitimate user. Rate limiting is therefore suspended for the
 * 'unknown' case and a one-time operator warning is emitted instead,
 * mirroring the identical policy applied to API routes in router.js.
 *
 * Set ZEN_TRUST_PROXY=true only behind a verified reverse proxy (Nginx,
 * Cloudflare, AWS ALB, …) that strips and rewrites forwarding headers.
 *
 * @param {string} ip
 * @param {string} action
 * @returns {{ allowed: boolean, retryAfterMs?: number } | null} null = suspended
 */
function enforceRateLimit(ip, action) {
  if (ip === 'unknown') {
    if (!_rateLimitUnavailableWarned) {
      _rateLimitUnavailableWarned = true;
      fail(
        'Rate limiting inactive (server actions): client IP cannot be determined. ' +
        'Set ZEN_TRUST_PROXY=true behind a verified reverse proxy to enable per-IP rate limiting.'
      );
    }
    return null;
  }
  return checkRateLimit(ip, action);
}

/**
 * Validate anti-bot fields submitted with forms.
 * - _hp  : honeypot field — must be empty
 * - _t   : form load timestamp (ms) — submission must be at least 1.5 s after page
 *          load AND no more than MAX_FORM_AGE_MS in the past. Both a lower bound
 *          (prevents instant automated submission) and an upper bound (prevents the
 *          trivial bypass of supplying an arbitrary past timestamp such as _t=1) are
 *          enforced. Future timestamps are also rejected.
 *
 * @param {FormData} formData
 * @returns {{ valid: boolean, error?: string }}
 */
function validateAntiBotFields(formData) {
  const honeypot = formData.get('_hp');
  if (honeypot && honeypot.length > 0) {
    return { valid: false, error: 'Requête invalide' };
  }

  const MIN_ELAPSED_MS  = 1_500;
  const MAX_FORM_AGE_MS = 10 * 60 * 1_000; // 10 minutes — rejects epoch-era timestamps
  const now = Date.now();
  const t   = parseInt(formData.get('_t') || '0', 10);
  const elapsed = now - t;

  if (t === 0 || t > now || elapsed < MIN_ELAPSED_MS || elapsed > MAX_FORM_AGE_MS) {
    return { valid: false, error: 'Requête invalide' };
  }

  return { valid: true };
}

// Get cookie name from environment or use default
export const COOKIE_NAME = getSessionCookieName();

/**
 * Register a new user
 * @param {FormData} formData - Form data with email, password, name
 * @returns {Promise<Object>} Result object
 */
export async function registerAction(formData) {
  try {
    const botCheck = validateAntiBotFields(formData);
    if (!botCheck.valid) return { success: false, error: botCheck.error };

    const ip = await getClientIp();
    const rl = enforceRateLimit(ip, 'register');
    if (rl && !rl.allowed) {
      return { success: false, error: `Trop de tentatives. Réessayez dans ${formatRetryAfter(rl.retryAfterMs)}.` };
    }

    const email = formData.get('email');
    const password = formData.get('password');
    const name = formData.get('name');
    
    const result = await register({ email, password, name });
    
    // Send verification email
    await sendVerificationEmail(result.user.email, result.verificationToken, getPublicBaseUrl());
    
    return {
      success: true,
      message: 'Compte créé avec succès. Consultez votre e-mail pour vérifier votre compte.',
      user: result.user
    };
  } catch (error) {
    // Never return raw error.message to the client — library and database errors
    // (e.g. unique-constraint violations) expose internal table names and schema.
    fail(`Auth: registerAction error: ${error.message}`);
    return { success: false, error: 'Une erreur interne est survenue. Veuillez réessayer.' };
  }
}

/**
 * Login a user
 * @param {FormData} formData - Form data with email and password
 * @returns {Promise<Object>} Result object
 */
export async function loginAction(formData) {
  try {
    const botCheck = validateAntiBotFields(formData);
    if (!botCheck.valid) return { success: false, error: botCheck.error };

    const ip = await getClientIp();
    const rl = enforceRateLimit(ip, 'login');
    if (rl && !rl.allowed) {
      return { success: false, error: `Trop de tentatives. Réessayez dans ${formatRetryAfter(rl.retryAfterMs)}.` };
    }

    const email = formData.get('email');
    const password = formData.get('password');
    
    const result = await login({ email, password });

    // Set the session cookie directly inside this server action so the token
    // never travels through JavaScript-readable response payload.
    // An HttpOnly cookie is the only safe transport for session tokens.
    const cookieStore = await cookies();
    cookieStore.set(COOKIE_NAME, result.session.token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 60 * 60 * 24 * 30,
      path: '/'
    });

    return {
      success: true,
      message: 'Connexion réussie',
      user: result.user
      // sessionToken intentionally omitted — it must never appear in a
      // JavaScript-accessible response body.
    };
  } catch (error) {
    fail(`Auth: loginAction error: ${error.message}`);
    return { success: false, error: 'Identifiants invalides ou erreur interne. Veuillez réessayer.' };
  }
}

/**
 * Set session cookie after verifying the token is a genuine live session.
 *
 * This server action is client-callable. Without server-side token validation an
 * attacker could supply any arbitrary string (including a stolen token for another
 * user) and have it written as the HttpOnly session cookie, completely bypassing
 * the protection that HttpOnly is intended to provide. The token is therefore
 * validated against the session store before the cookie is written.
 *
 * @param {string} token - Session token obtained from a trusted server-side flow
 * @returns {Promise<Object>} Result object
 */
export async function setSessionCookie(token) {
  try {
    if (!token || typeof token !== 'string' || token.trim() === '') {
      return { success: false, error: 'Jeton de session invalide' };
    }

    const session = await validateSession(token);
    if (!session) {
      return { success: false, error: 'Session invalide ou expirée' };
    }

    const cookieStore = await cookies();
    cookieStore.set(COOKIE_NAME, token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 60 * 60 * 24 * 30, // 30 days
      path: '/'
    });
    
    return { success: true };
  } catch (error) {
    fail(`Auth: setSessionCookie error: ${error.message}`);
    return { success: false, error: 'Une erreur interne est survenue' };
  }
}

/**
 * Refresh session cookie (extend expiration).
 *
 * Re-validates the token before extending its cookie lifetime so that expired
 * or revoked tokens cannot have their cookie window reopened by replaying this
 * server action.
 *
 * @param {string} token - Session token
 * @returns {Promise<Object>} Result object
 */
export async function refreshSessionCookie(token) {
  try {
    if (!token || typeof token !== 'string' || token.trim() === '') {
      return { success: false, error: 'Jeton de session invalide' };
    }

    const session = await validateSession(token);
    if (!session) {
      return { success: false, error: 'Session invalide ou expirée' };
    }

    const cookieStore = await cookies();
    cookieStore.set(COOKIE_NAME, token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 60 * 60 * 24 * 30, // 30 days
      path: '/'
    });
    
    return { success: true };
  } catch (error) {
    fail(`Auth: refreshSessionCookie error: ${error.message}`);
    return { success: false, error: 'Une erreur interne est survenue' };
  }
}

/**
 * Logout a user
 * @returns {Promise<Object>} Result object
 */
export async function logoutAction() {
  try {
    const cookieStore = await cookies();
    const token = cookieStore.get(COOKIE_NAME)?.value;
    
    if (token) {
      await deleteSession(token);
    }
    
    cookieStore.delete(COOKIE_NAME);
    
    return {
      success: true,
      message: 'Déconnexion réussie'
    };
  } catch (error) {
    fail(`Auth: logoutAction error: ${error.message}`);
    return { success: false, error: 'Une erreur interne est survenue. Veuillez réessayer.' };
  }
}

/**
 * Get current user session
 * @returns {Promise<Object|null>} Session and user data or null
 */
export async function getSession() {
  try {
    const cookieStore = await cookies();
    const token = cookieStore.get(COOKIE_NAME)?.value;
    
    if (!token) return null;
    
    const result = await validateSession(token);
    
    // If session was refreshed, also refresh the cookie
    if (result && result.sessionRefreshed) {
      await refreshSessionCookie(token);
    }
    
    return result;
  } catch (error) {
    fail(`Auth: session validation error: ${error.message}`);
    return null;
  }
}

/**
 * Request password reset
 * @param {FormData} formData - Form data with email
 * @returns {Promise<Object>} Result object
 */
export async function forgotPasswordAction(formData) {
  try {
    const botCheck = validateAntiBotFields(formData);
    if (!botCheck.valid) return { success: false, error: botCheck.error };

    const ip = await getClientIp();
    const rl = enforceRateLimit(ip, 'forgot_password');
    if (rl && !rl.allowed) {
      return { success: false, error: `Trop de tentatives. Réessayez dans ${formatRetryAfter(rl.retryAfterMs)}.` };
    }

    const email = formData.get('email');
    
    const result = await requestPasswordReset(email);
    
    if (result.token) {
      await sendPasswordResetEmail(email, result.token, getPublicBaseUrl());
    }
    
    return {
      success: true,
      message: 'Si un compte existe avec cet e-mail, vous recevrez un lien pour réinitialiser votre mot de passe.'
    };
  } catch (error) {
    fail(`Auth: forgotPasswordAction error: ${error.message}`);
    return { success: false, error: 'Une erreur interne est survenue. Veuillez réessayer.' };
  }
}

/**
 * Reset password with token
 * @param {FormData} formData - Form data with email, token, and newPassword
 * @returns {Promise<Object>} Result object
 */
export async function resetPasswordAction(formData) {
  try {
    const ip = await getClientIp();
    const rl = enforceRateLimit(ip, 'reset_password');
    if (rl && !rl.allowed) {
      return { success: false, error: `Trop de tentatives. Réessayez dans ${formatRetryAfter(rl.retryAfterMs)}.` };
    }

    const email = formData.get('email');
    const token = formData.get('token');
    const newPassword = formData.get('newPassword');
    
    // Verify token first — throw UserFacingError so the specific message reaches
    // the client while unexpected system errors are sanitized in the catch below.
    const isValid = await verifyResetToken(email, token);
    if (!isValid) {
      throw new UserFacingError('Jeton de réinitialisation invalide ou expiré');
    }
    
    await resetPassword({ email, token, newPassword });
    
    return {
      success: true,
      message: 'Mot de passe réinitialisé avec succès. Vous pouvez maintenant vous connecter avec votre nouveau mot de passe.'
    };
  } catch (error) {
    if (error instanceof UserFacingError) {
      return { success: false, error: error.message };
    }
    fail(`Auth: resetPasswordAction error: ${error.message}`);
    return { success: false, error: 'Une erreur interne est survenue. Veuillez réessayer.' };
  }
}

/**
 * Verify email with token
 * @param {FormData} formData - Form data with email and token
 * @returns {Promise<Object>} Result object
 */
export async function verifyEmailAction(formData) {
  try {
    const ip = await getClientIp();
    const rl = enforceRateLimit(ip, 'verify_email');
    if (rl && !rl.allowed) {
      return { success: false, error: `Trop de tentatives. Réessayez dans ${formatRetryAfter(rl.retryAfterMs)}.` };
    }

    const email = formData.get('email');
    const token = formData.get('token');
    
    // Verify token — throw UserFacingError so validation messages surface to the
    // client while unexpected system errors remain sanitized in the catch below.
    const isValid = await verifyEmailToken(email, token);
    if (!isValid) {
      throw new UserFacingError('Jeton de vérification invalide ou expiré');
    }
    
    // Find user and verify
    const { findOne } = await import('../../../core/database/crud.js');
    const user = await findOne('zen_auth_users', { email });
    
    if (!user) {
      throw new UserFacingError('Utilisateur introuvable');
    }
    
    await verifyUserEmail(user.id);
    
    return {
      success: true,
      message: 'E-mail vérifié avec succès. Vous pouvez maintenant vous connecter.'
    };
  } catch (error) {
    if (error instanceof UserFacingError) {
      return { success: false, error: error.message };
    }
    fail(`Auth: verifyEmailAction error: ${error.message}`);
    return { success: false, error: 'Une erreur interne est survenue. Veuillez réessayer.' };
  }
}

