import speakeasy from 'speakeasy';
import bcrypt from 'bcrypt';
import { db, schema } from './db';
import { eq, lt } from 'drizzle-orm';

// 2FA Challenge expiry: 10 minutes
const CHALLENGE_EXPIRY_MINUTES = 10;
const MAX_ATTEMPTS = 5;

export type TwoFactorMethod = 'email' | 'sms' | 'authenticator';

/**
 * Generate a TOTP secret for authenticator app setup
 */
export function generateTOTPSecret(): { secret: string; otpauthUrl: string } {
  const secret = speakeasy.generateSecret({
    name: 'Scriba AI',
    length: 32,
  });

  return {
    secret: secret.base32,
    otpauthUrl: secret.otpauth_url || '',
  };
}

/**
 * Verify a TOTP code against a secret
 */
export function verifyTOTP(token: string, secret: string): boolean {
  return speakeasy.totp.verify({
    secret,
    encoding: 'base32',
    token,
    window: 2, // Allow 1 step before/after for clock drift
  });
}

/**
 * Generate a 6-digit verification code
 */
export function generateVerificationCode(): string {
  return Math.floor(100000 + Math.random() * 900000).toString();
}

/**
 * Hash a verification code for storage
 */
export async function hashCode(code: string): Promise<string> {
  return bcrypt.hash(code, 10);
}

/**
 * Verify a code against its hash
 */
export async function verifyCode(code: string, hash: string): Promise<boolean> {
  return bcrypt.compare(code, hash);
}

/**
 * Create a 2FA challenge for a user
 */
export async function create2FAChallenge(
  userId: string,
  method: TwoFactorMethod,
  code?: string,
  ipAddress?: string,
  userAgent?: string
): Promise<string> {
  const challengeId = crypto.randomUUID();
  const codeHash = code ? await hashCode(code) : null;
  const expiresAt = new Date(Date.now() + CHALLENGE_EXPIRY_MINUTES * 60 * 1000);

  await db.insert(schema.twoFactorChallenges).values({
    id: challengeId,
    userId,
    method,
    codeHash,
    expiresAt,
    ipAddress,
    userAgent,
    attempts: 0,
    verified: false,
  });

  return challengeId;
}

/**
 * Get a 2FA challenge by ID
 */
export async function get2FAChallenge(challengeId: string) {
  return db.query.twoFactorChallenges.findFirst({
    where: eq(schema.twoFactorChallenges.id, challengeId),
  });
}

/**
 * Verify a 2FA challenge code
 * Returns { success: boolean, message: string }
 */
export async function verify2FAChallenge(
  challengeId: string,
  code: string
): Promise<{ success: boolean; message: string }> {
  const challenge = await get2FAChallenge(challengeId);

  if (!challenge) {
    return { success: false, message: 'Challenge not found' };
  }

  // Check if already verified
  if (challenge.verified) {
    return { success: false, message: 'Challenge already verified' };
  }

  // Check if expired
  if (new Date() > new Date(challenge.expiresAt)) {
    return { success: false, message: 'Challenge expired' };
  }

  // Check max attempts
  const attempts = challenge.attempts ?? 0;
  if (attempts >= MAX_ATTEMPTS) {
    return { success: false, message: 'Too many attempts. Please request a new code.' };
  }

  // Increment attempts
  await db
    .update(schema.twoFactorChallenges)
    .set({ attempts: attempts + 1 })
    .where(eq(schema.twoFactorChallenges.id, challengeId));

  // Verify based on method
  let isValid = false;

  if (challenge.method === 'authenticator') {
    // For authenticator, get user's TOTP secret and verify
    const user = await db.query.users.findFirst({
      where: eq(schema.users.id, challenge.userId),
    });
    if (user?.twoFactorSecret) {
      isValid = verifyTOTP(code, user.twoFactorSecret);
    }
  } else {
    // For email/sms, verify against stored code hash
    if (challenge.codeHash) {
      isValid = await verifyCode(code, challenge.codeHash);
    }
  }

  if (!isValid) {
    return { success: false, message: 'Invalid verification code' };
  }

  // Mark as verified
  await db
    .update(schema.twoFactorChallenges)
    .set({ verified: true })
    .where(eq(schema.twoFactorChallenges.id, challengeId));

  return { success: true, message: 'Verification successful' };
}

/**
 * Clean up expired 2FA challenges
 */
export async function cleanupExpiredChallenges(): Promise<void> {
  const now = new Date();
  await db
    .delete(schema.twoFactorChallenges)
    .where(lt(schema.twoFactorChallenges.expiresAt, now));
}

/**
 * Send 2FA code via email (simulated - logs to console for demo)
 */
export async function sendEmail2FACode(email: string, code: string): Promise<void> {
  // TODO: Integrate with email service (SendGrid, AWS SES, etc.)
  void email; void code;
}

/**
 * Send 2FA code via SMS (simulated - logs to console for demo)
 */
export async function sendSMS2FACode(phoneNumber: string, code: string): Promise<void> {
  // TODO: Integrate with SMS service (Twilio, etc.)
  void phoneNumber; void code;
}

/**
 * Enable 2FA for a user
 */
export async function enable2FA(
  userId: string,
  method: TwoFactorMethod,
  secret?: string,
  phoneNumber?: string
): Promise<void> {
  const updateData: Partial<typeof schema.users.$inferInsert> = {
    twoFactorEnabled: true,
    twoFactorMethod: method,
  };

  if (method === 'authenticator' && secret) {
    updateData.twoFactorSecret = secret;
  }

  if (method === 'sms' && phoneNumber) {
    updateData.phoneNumber = phoneNumber;
  }

  await db
    .update(schema.users)
    .set(updateData)
    .where(eq(schema.users.id, userId));
}

/**
 * Disable 2FA for a user
 */
export async function disable2FA(userId: string): Promise<void> {
  await db
    .update(schema.users)
    .set({
      twoFactorEnabled: false,
      twoFactorMethod: null,
      twoFactorSecret: null,
    })
    .where(eq(schema.users.id, userId));
}

/**
 * Check if user has 2FA enabled
 */
export async function is2FAEnabled(userId: string): Promise<{ enabled: boolean; method: TwoFactorMethod | null }> {
  const user = await db.query.users.findFirst({
    where: eq(schema.users.id, userId),
  });

  return {
    enabled: user?.twoFactorEnabled ?? false,
    method: (user?.twoFactorMethod as TwoFactorMethod) || null,
  };
}
