/**
 * OTP Helper Functions
 *
 * Provides utilities for generating, verifying, and managing OTPs
 * in the Mawidi appointment management platform.
 *
 * @module lib/otp-helper
 */

import { prisma } from '@/lib/db';
type OtpPurpose = 'LOGIN' | 'SIGNUP' | 'RESET_PASSWORD' | 'EMAIL_VERIFICATION' | 'PHONE_VERIFICATION';
import crypto from 'crypto';

/**
 * OTP Configuration
 */
const OTP_CONFIG = {
  LENGTH: 6,
  EXPIRY_MINUTES: 10,
  MAX_ATTEMPTS: 5,
  RATE_LIMIT_WINDOW_MINUTES: 60,
  MAX_REQUESTS_PER_WINDOW: 5,
} as const;

/**
 * Result types
 */
export interface OtpGenerationResult {
  success: boolean;
  otpCode?: string;
  otpId?: string;
  expiresAt?: Date;
  error?: string;
}

export interface OtpVerificationResult {
  success: boolean;
  error?: string;
  otpId?: string;
}

export interface RateLimitResult {
  allowed: boolean;
  remaining?: number;
  resetAt?: Date;
}

/**
 * Generate a cryptographically secure 6-digit OTP code
 *
 * @returns A 6-digit numeric string
 */
export function generateSecureOTP(): string {
  // Generate random bytes and convert to number
  const randomBytes = crypto.randomBytes(4);
  const randomNumber = randomBytes.readUInt32BE(0);

  // Ensure 6 digits with leading zeros
  const otpCode = (randomNumber % 1000000).toString().padStart(OTP_CONFIG.LENGTH, '0');

  return otpCode;
}

/**
 * Calculate OTP expiration timestamp
 *
 * @param minutes - Minutes until expiration (default: 10)
 * @returns Expiration timestamp
 */
export function calculateExpiryTime(minutes: number = OTP_CONFIG.EXPIRY_MINUTES): Date {
  return new Date(Date.now() + minutes * 60 * 1000);
}

/**
 * Check if rate limit allows OTP generation for an email
 *
 * @param email - Email address to check
 * @returns Rate limit status
 */
export async function checkRateLimit(email: string): Promise<RateLimitResult> {
  const windowStart = new Date(Date.now() - OTP_CONFIG.RATE_LIMIT_WINDOW_MINUTES * 60 * 1000);

  const recentOTPs = await prisma.otps.count({
    where: {
      email: email.toLowerCase(),
      createdAt: {
        gte: windowStart,
      },
    },
  });

  const allowed = recentOTPs < OTP_CONFIG.MAX_REQUESTS_PER_WINDOW;
  const remaining = Math.max(0, OTP_CONFIG.MAX_REQUESTS_PER_WINDOW - recentOTPs);
  const resetAt = new Date(Date.now() + OTP_CONFIG.RATE_LIMIT_WINDOW_MINUTES * 60 * 1000);

  return {
    allowed,
    remaining,
    resetAt,
  };
}

/**
 * Generate and store a new OTP
 *
 * @param email - Email address for OTP
 * @param purpose - Purpose of the OTP (SIGNUP, LOGIN, PASSWORD_RESET)
 * @param ipAddress - Optional IP address for security tracking
 * @returns Generation result with OTP code or error
 */
export async function generateOTP(
  email: string,
  purpose: OtpPurpose,
  ipAddress?: string
): Promise<OtpGenerationResult> {
  try {
    // Normalize email
    const normalizedEmail = email.toLowerCase().trim();

    // Check rate limit
    const rateLimit = await checkRateLimit(normalizedEmail);
    if (!rateLimit.allowed) {
      return {
        success: false,
        error: `Too many OTP requests. Please try again after ${rateLimit.resetAt?.toLocaleTimeString()}.`,
      };
    }

    // Generate OTP
    const otpCode = generateSecureOTP();
    const expiresAt = calculateExpiryTime();

    // Store in database
    const otp = await prisma.otps.create({
      data: {
        email: normalizedEmail,
        otpCode,
        purpose,
        expiresAt,
        ipAddress,
      },
    });

    return {
      success: true,
      otpCode,
      otpId: otp.id,
      expiresAt,
    };
  } catch (error) {
    console.error('Error generating OTP:', error);
    return {
      success: false,
      error: 'Failed to generate OTP. Please try again.',
    };
  }
}

/**
 * Verify an OTP code
 *
 * @param email - Email address
 * @param code - OTP code to verify
 * @param purpose - Expected purpose of the OTP
 * @returns Verification result
 */
export async function verifyOTP(
  email: string,
  code: string,
  purpose: OtpPurpose
): Promise<OtpVerificationResult> {
  try {
    // Normalize inputs
    const normalizedEmail = email.toLowerCase().trim();
    const normalizedCode = code.trim();

    // Find valid OTP
    const otp = await prisma.otps.findFirst({
      where: {
        email: normalizedEmail,
        otpCode: normalizedCode,
        purpose,
        verified: false,
        expiresAt: {
          gte: new Date(), // Not expired
        },
      },
      orderBy: {
        createdAt: 'desc', // Most recent
      },
    });

    if (!otp) {
      return {
        success: false,
        error: 'Invalid or expired OTP code.',
      };
    }

    // Mark as verified
    await prisma.otps.update({
      where: { id: otp.id },
      data: {
        verified: true,
        verifiedAt: new Date(),
      },
    });

    return {
      success: true,
      otpId: otp.id,
    };
  } catch (error) {
    console.error('Error verifying OTP:', error);
    return {
      success: false,
      error: 'Failed to verify OTP. Please try again.',
    };
  }
}

/**
 * Invalidate all unverified OTPs for an email and purpose
 * Useful when user successfully verifies one OTP
 *
 * @param email - Email address
 * @param purpose - OTP purpose
 */
export async function invalidateUnverifiedOTPs(
  email: string,
  purpose: OtpPurpose
): Promise<void> {
  const normalizedEmail = email.toLowerCase().trim();

  await prisma.otps.updateMany({
    where: {
      email: normalizedEmail,
      purpose,
      verified: false,
    },
    data: {
      expiresAt: new Date(0), // Set to past date to expire
    },
  });
}

/**
 * Clean up expired OTPs
 * Should be run periodically (e.g., daily cron job)
 *
 * @returns Number of deleted OTPs
 */
export async function cleanupExpiredOTPs(): Promise<number> {
  try {
    const result = await prisma.otps.deleteMany({
      where: {
        expiresAt: {
          lt: new Date(), // Expired
        },
      },
    });

    console.log(`Cleaned up ${result.count} expired OTPs`);
    return result.count;
  } catch (error) {
    console.error('Error cleaning up expired OTPs:', error);
    return 0;
  }
}

/**
 * Get OTP statistics for monitoring
 *
 * @returns OTP statistics
 */
export async function getOTPStats() {
  const [total, verified, expired, byPurpose] = await Promise.all([
    // Total OTPs
    prisma.otps.count(),

    // Verified OTPs
    prisma.otps.count({
      where: { verified: true },
    }),

    // Expired OTPs
    prisma.otps.count({
      where: {
        expiresAt: {
          lt: new Date(),
        },
      },
    }),

    // By purpose
    prisma.otps.groupBy({
      by: ['purpose'],
      _count: {
        purpose: true,
      },
    }),
  ]);

  return {
    total,
    verified,
    expired,
    unverified: total - verified,
    byPurpose,
  };
}

/**
 * Check if an OTP exists and is valid (not expired, not verified)
 *
 * @param email - Email address
 * @param purpose - OTP purpose
 * @returns True if valid OTP exists
 */
export async function hasValidOTP(
  email: string,
  purpose: OtpPurpose
): Promise<boolean> {
  const normalizedEmail = email.toLowerCase().trim();

  const count = await prisma.otps.count({
    where: {
      email: normalizedEmail,
      purpose,
      verified: false,
      expiresAt: {
        gte: new Date(),
      },
    },
  });

  return count > 0;
}

/**
 * Get time remaining for the most recent valid OTP
 *
 * @param email - Email address
 * @param purpose - OTP purpose
 * @returns Seconds remaining or null if no valid OTP
 */
export async function getTimeRemaining(
  email: string,
  purpose: OtpPurpose
): Promise<number | null> {
  const normalizedEmail = email.toLowerCase().trim();

  const otp = await prisma.otps.findFirst({
    where: {
      email: normalizedEmail,
      purpose,
      verified: false,
      expiresAt: {
        gte: new Date(),
      },
    },
    orderBy: {
      createdAt: 'desc',
    },
    select: {
      expiresAt: true,
    },
  });

  if (!otp) return null;

  const now = Date.now();
  const expiryTime = otp.expiresAt.getTime();
  const remainingMs = expiryTime - now;

  return Math.max(0, Math.floor(remainingMs / 1000));
}

/**
 * Export configuration for reference
 */
export { OTP_CONFIG };
