/**
 * Secure Token Generation and Verification
 *
 * Replaces insecure base64-encoded JSON with cryptographically signed JWTs
 * using the jose library and HS256 algorithm.
 *
 * Reset token state tracking uses Convex instead of Redis (T14).
 *
 * Security improvements:
 * - Cryptographic signatures prevent token forgery
 * - Expiration timestamps prevent replay attacks
 * - Issued-at claims for audit trail
 * - Environment-based secret management
 *
 * @module lib/secure-tokens
 */

import { SignJWT, jwtVerify, errors } from 'jose';
import { randomBytes } from 'crypto';
import { logger } from './logger';
import { convexMutation } from './convex-client';

/**
 * Generate a UUID v4 string
 * Falls back to manual generation if crypto.randomUUID is unavailable
 */
function generateUUID(): string {
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID();
  }
  // Fallback for Node.js environments without randomUUID
  const bytes = randomBytes(16);
  bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
  bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10
  return [
    bytes.slice(0, 4).toString('hex'),
    bytes.slice(4, 6).toString('hex'),
    bytes.slice(6, 8).toString('hex'),
    bytes.slice(8, 10).toString('hex'),
    bytes.slice(10, 16).toString('hex'),
  ].join('-');
}

/**
 * Get JWT secret from environment
 * Throws error in production if secret is missing
 * Allows fallback in development with warning
 */
function getJWTSecret(): Uint8Array {
  const secret = process.env.NEXTAUTH_SECRET;

  if (!secret) {
    if (process.env.NODE_ENV === 'production') {
      throw new Error(
        'CRITICAL SECURITY ERROR: NEXTAUTH_SECRET is required in production. ' +
        'Generate with: openssl rand -base64 32'
      );
    }

    // Development fallback with warning
    logger.warn(
      'SECURITY WARNING: NEXTAUTH_SECRET not set. Using development fallback. ' +
      'Set NEXTAUTH_SECRET in .env.local for production-like security.'
    );
    return new TextEncoder().encode('dev-secret-do-not-use-in-production');
  }

  return new TextEncoder().encode(secret);
}

/**
 * Generate a secure JWT token with cryptographic signature
 *
 * @param payload - Data to encode in the token
 * @param expiresIn - Token expiration time (e.g., '15m', '1h', '7d')
 * @returns Signed JWT token string
 */
export async function generateSecureToken(
  payload: Record<string, unknown>,
  expiresIn: string = '15m'
): Promise<string> {
  try {
    const secret = getJWTSecret();

    // Parse expiration string to seconds
    const expiresInSeconds = parseExpirationToSeconds(expiresIn);

    // Create JWT with standard claims
    const jwt = await new SignJWT(payload)
      .setProtectedHeader({ alg: 'HS256' })
      .setIssuedAt() // iat claim
      .setExpirationTime(`${expiresInSeconds}s`) // exp claim
      .setIssuer('mawidi-auth') // iss claim for verification
      .sign(secret);

    logger.info('Secure token generated', {
      expiresIn,
      hasUserId: !!payload.userId,
      hasEmail: !!payload.email,
    });

    return jwt;
  } catch (error) {
    logger.error('Failed to generate secure token', error);
    if (error instanceof Error) {
      throw error;
    }
    throw new Error('Token generation failed');
  }
}

/**
 * Verify and decode a secure JWT token
 *
 * @param token - JWT token string to verify
 * @returns Decoded payload if valid, null if invalid/expired
 */
export async function verifySecureToken(
  token: string
): Promise<Record<string, unknown> | null> {
  try {
    const secret = getJWTSecret();

    // Verify signature and expiration
    const { payload } = await jwtVerify(token, secret, {
      issuer: 'mawidi-auth', // Verify issuer matches
    });

    logger.info('Token verified successfully', {
      hasUserId: !!payload.userId,
      hasEmail: !!payload.email,
    });

    // Convert payload to plain object
    return payload as Record<string, unknown>;
  } catch (error) {
    // Log specific error types for debugging
    if (error instanceof errors.JWTExpired) {
      logger.warn('Token expired', { token: token.substring(0, 20) + '...' });
    } else if (error instanceof errors.JWTInvalid) {
      logger.warn('Invalid token signature', { token: token.substring(0, 20) + '...' });
    } else {
      logger.error('Token verification failed', error);
    }

    return null;
  }
}

/**
 * Parse expiration string (e.g., '15m', '1h', '7d') to seconds
 *
 * @param expiresIn - Expiration string
 * @returns Number of seconds
 */
function parseExpirationToSeconds(expiresIn: string): number {
  const match = expiresIn.match(/^(\d+)([smhd])$/);

  if (!match) {
    throw new Error(`Invalid expiration format: ${expiresIn}. Use format like '15m', '1h', '7d'`);
  }

  const value = parseInt(match[1], 10);
  const unit = match[2];

  switch (unit) {
    case 's': return value; // seconds
    case 'm': return value * 60; // minutes
    case 'h': return value * 60 * 60; // hours
    case 'd': return value * 60 * 60 * 24; // days
    default: throw new Error(`Unknown time unit: ${unit}`);
  }
}

/**
 * Generate a secure password reset token with Convex state tracking
 *
 * This creates a JWT token AND stores its state in Convex to prevent replay attacks.
 * The token can only be used once, even if intercepted.
 *
 * @param identifier - User email or phone number
 * @param type - Identifier type ('email' or 'phone')
 * @returns Reset token
 */
export async function generateResetToken(
  identifier: string,
  type: 'email' | 'phone'
): Promise<string> {
  // Generate unique token ID
  const tokenId = generateUUID();

  // Create JWT with token ID
  const token = await generateSecureToken(
    {
      identifier,
      type,
      tokenId,
      purpose: 'password-reset',
    },
    '10m' // Reset tokens expire in 10 minutes
  );

  // Store token in Convex (marks token as valid and unused)
  const expiresAt = Date.now() + 600 * 1000; // 10 minutes
  await convexMutation("auth/mutations:createResetToken", {
    email: identifier,
    token: tokenId, // Store the tokenId, not the full JWT
    expiresAt,
  });

  logger.info('Reset token generated and tracked in Convex', {
    tokenId,
    type,
    hasIdentifier: !!identifier,
  });

  return token;
}

/**
 * Verify and consume a password reset token
 *
 * This verifies the JWT signature AND checks Convex to ensure the token
 * hasn't been used before. Once verified, the token is immediately consumed
 * in Convex, preventing replay attacks.
 *
 * @param token - Reset token to verify
 * @returns Decoded payload if valid and unused, null otherwise
 */
export async function verifyResetToken(
  token: string
): Promise<Record<string, unknown> | null> {
  // First, verify JWT signature and expiration
  const payload = await verifySecureToken(token);

  if (!payload || payload.purpose !== 'password-reset') {
    logger.warn('Invalid reset token: signature verification failed');
    return null;
  }

  // Consume the token in Convex (returns token data if valid, null if already used/expired)
  const result = await convexMutation<{ token: string; email: string } | null>(
    "auth/mutations:consumeResetToken",
    { email: payload.identifier as string }
  );

  if (!result) {
    logger.warn('Reset token already used or expired', {
      tokenId: payload.tokenId,
    });
    return null;
  }

  // Verify identifier matches
  if (result.email !== payload.identifier) {
    logger.error('Reset token identifier mismatch', {
      tokenId: payload.tokenId,
    });
    return null;
  }

  logger.info('Reset token verified and consumed', {
    tokenId: payload.tokenId,
    type: payload.type,
  });

  return payload;
}
