/**
 * Authentication Helper Utilities
 *
 * Reusable authentication-related utilities following DRY principles
 * Extracted common patterns from auth endpoints
 *
 * @module lib/utils/auth-helpers.util
 */

import { NextRequest } from 'next/server';
import { prisma } from '@/lib/db';
import type { Language } from '@/lib/types/otp.types';
import { logger } from '@/lib/logger';

/** JSON-compatible input value (replaces Prisma.InputJsonValue) */
type InputJsonValue = string | number | boolean | null | InputJsonValue[] | { [key: string]: InputJsonValue };

/**
 * User data structure for authentication
 */
export interface AuthUser {
  id: string;
  email: string;
  name: string;
  emailVerified: Date | null;
  googleId: string | null;
}

/**
 * Extract client IP address from request headers
 *
 * Checks multiple header sources in order of preference
 *
 * @param request - Next.js request object
 * @returns Client IP address or fallback to localhost
 *
 * @example
 * ```typescript
 * const ip = getClientIP(request);
 * console.log(`Request from: ${ip}`);
 * ```
 */
export function getClientIP(request: NextRequest): string {
  return (
    request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
    request.headers.get('x-real-ip') ||
    request.headers.get('cf-connecting-ip') || // Cloudflare
    '127.0.0.1'
  );
}

/**
 * Extract user agent from request headers
 *
 * @param request - Next.js request object
 * @returns User agent string or undefined
 */
export function getUserAgent(request: NextRequest): string | undefined {
  return request.headers.get('user-agent') || undefined;
}

/**
 * Create audit log entry for authentication events
 *
 * @param userId - User ID (optional for signup events)
 * @param type - Event type identifier
 * @param action - Human-readable action description
 * @param success - Whether the operation succeeded
 * @param request - Next.js request object for IP/UA extraction
 * @param metadata - Additional event metadata
 *
 * @example
 * ```typescript
 * await createAuditLog(
 *   user.id,
 *   'login_success_otp',
 *   'User logged in with email OTP',
 *   true,
 *   request,
 *   { method: 'otp', identifierType: 'email' }
 * );
 * ```
 */
export async function createAuditLog(
  userId: string | null | undefined,
  type: string,
  action: string,
  success: boolean,
  request: NextRequest,
  metadata?: InputJsonValue
): Promise<void> {
  try {
    await prisma.audit_logs.create({
      data: {
        id: crypto.randomUUID(), // Generate unique ID for audit log
        userId: userId || undefined,
        type,
        action,
        success,
        ip: getClientIP(request),
        userAgent: getUserAgent(request),
        metadata: metadata ?? undefined,
      },
    });
  } catch (error) {
    // Audit logging should not break the main flow
    logger.error('Audit log creation failed', error);
  }
}

/**
 * Find user by email or phone number
 *
 * @param identifier - Email address or phone number
 * @param validateEmail - Email validation function
 * @returns User object or null
 *
 * @example
 * ```typescript
 * const user = await findUserByIdentifier(
 *   'user@example.com',
 *   validateEmail
 * );
 * ```
 */
export async function findUserByIdentifier(
  identifier: string,
  validateEmail: (email: string) => { valid: boolean }
): Promise<AuthUser | null> {
  const isEmail = validateEmail(identifier).valid;

  return await prisma.users.findFirst({
    where: isEmail
      ? { email: identifier }
      : { phoneNumber: identifier },
    select: {
      id: true,
      email: true,
      name: true,
      emailVerified: true,
      googleId: true,
    },
  });
}

/**
 * Update user's last login information
 *
 * @param userId - User ID
 * @param request - Next.js request object
 *
 * @example
 * ```typescript
 * await updateLastLogin(user.id, request);
 * ```
 */
export async function updateLastLogin(
  userId: string,
  request: NextRequest
): Promise<void> {
  await prisma.users.update({
    where: { id: userId },
    data: {
      lastLoginAt: new Date(),
      lastLoginIp: getClientIP(request),
      failedAttempts: 0,
      lockedUntil: null,
    },
  });
}

/**
 * Extract language from request body or default to English
 *
 * @param body - Request body object
 * @returns Language code ('ar' or 'en')
 */
export function extractLanguage(body: unknown): Language {
  if (body && typeof body === 'object') {
    const bodyObj = body as Record<string, unknown>;
    const lang = bodyObj.language || bodyObj.lang || 'en';
    return lang === 'ar' ? 'ar' : 'en';
  }
  return 'en';
}

/**
 * Sanitize and normalize email address
 *
 * @param email - Email address to normalize
 * @returns Normalized email (lowercase, trimmed)
 */
export function normalizeEmail(email: string): string {
  return email.toLowerCase().trim();
}

/**
 * Check if account is locked due to failed attempts
 *
 * @param user - User object with lockedUntil field
 * @returns Object with lock status and remaining minutes
 */
export function checkAccountLock(user: { lockedUntil: Date | null }): {
  isLocked: boolean;
  remainingMinutes: number;
} {
  if (!user.lockedUntil || new Date() >= user.lockedUntil) {
    return { isLocked: false, remainingMinutes: 0 };
  }

  const remainingMs = user.lockedUntil.getTime() - Date.now();
  const remainingMinutes = Math.ceil(remainingMs / (60 * 1000));

  return { isLocked: true, remainingMinutes };
}

/**
 * Generate secure JWT token for session
 *
 * Uses cryptographically signed JWT tokens with the jose library.
 * Tokens are signed with HS256 and include expiration.
 *
 * @param payload - Token payload
 * @returns Signed JWT token (use async wrapper generateSessionTokenAsync)
 * @deprecated Use generateSessionTokenAsync instead (async version required for JWT signing)
 */
export function generateSessionToken(payload: Record<string, unknown>): string {
  void payload;
  // This is now deprecated - use async version
  throw new Error(
    'generateSessionToken is deprecated. Use generateSessionTokenAsync from @/lib/secure-tokens instead.'
  );
}

/**
 * Generate secure JWT token for session (async)
 *
 * This is the replacement for generateSessionToken that uses proper JWT signing.
 * Import from @/lib/secure-tokens instead.
 *
 * @param payload - Token payload
 * @param expiresIn - Token expiration (default: 7 days for session tokens)
 * @returns Signed JWT token
 *
 * @example
 * ```typescript
 * import { generateSecureToken } from '@/lib/secure-tokens';
 *
 * const token = await generateSecureToken(
 *   { userId: user.id, email: user.email },
 *   '7d'
 * );
 * ```
 */
export async function generateSessionTokenAsync(
  payload: Record<string, unknown>,
  expiresIn: string = '7d'
): Promise<string> {
  const { generateSecureToken } = await import('@/lib/secure-tokens');
  return generateSecureToken(payload, expiresIn);
}

/**
 * Authenticated user data from the current Clerk-backed session.
 */
export interface AuthenticatedUser {
  userId: string;
  email: string;
  name?: string;
  stripeCustomerId?: string;
  source: 'clerk';
}

/**
 * Session structure for the Clerk-backed auth compatibility layer.
 */
export interface AuthSession {
  user?: {
    id?: string;
    email?: string;
    name?: string | null;
    stripeCustomerId?: string | null;
  };
}

/**
 * Type for the auth function - accepts the app's auth compatibility export.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AuthFunction = (...args: any[]) => Promise<AuthSession | null>;

export async function getAuthenticatedEmail(
  request: NextRequest,
  authFunction: AuthFunction
): Promise<string | null> {
  void request;
  const session = await authFunction();
  if (session?.user?.email) {
    return session.user.email;
  }

  try {
    const { currentUser } = await import('@clerk/nextjs/server');
    const clerkUser = await currentUser();
    if (clerkUser?.emailAddresses[0]?.emailAddress) {
      return clerkUser.emailAddresses[0].emailAddress.toLowerCase();
    }
  } catch {
    // Clerk not available or not authenticated.
  }

  return null;
}

/**
 * Get authenticated user data with full details (ID, email, Stripe customer ID).
 */
export async function getAuthenticatedUser(
  request: NextRequest,
  authFunction: AuthFunction
): Promise<AuthenticatedUser | null> {
  const { getAuthenticatedConvexUser } = await import('@/lib/convex-auth-state');
  const user = await getAuthenticatedConvexUser(request, authFunction);
  if (!user) return null;

  return {
    userId: user.id,
    email: user.email,
    name: user.name || undefined,
    stripeCustomerId: user.stripeCustomerId || undefined,
    source: 'clerk',
  };
}
