// src/routes/auth.routes.ts
import { Router, Request, Response, NextFunction } from 'express';
import * as bcrypt from 'bcrypt';
import zxcvbn from 'zxcvbn';
import { z } from 'zod';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import rateLimit from 'express-rate-limit';

import passport from './passport.routes'; // Corrected import path
import { userRepo, adminRepo } from '../services/db/index.db';
import { UniqueConstraintError } from '../services/db/errors.db';
import { getPool } from '../services/db/connection.db';
import { logger } from '../services/logger.server';
import { sendPasswordResetEmail } from '../services/emailService.server';
import { validateRequest } from '../middleware/validation.middleware';
import type { UserProfile } from '../types';

const router = Router();

const JWT_SECRET = process.env.JWT_SECRET!;

/**
 * Validates the strength of a password using zxcvbn.
 * @param password The password to check.
 * @returns An object with `isValid` and an optional `feedback` message.
 */
const validatePasswordStrength = (password: string): { isValid: boolean; feedback?: string } => {
  const MIN_PASSWORD_SCORE = 3; // Require a 'Good' or 'Strong' password (score 3 or 4)
  const strength = zxcvbn(password);

  if (strength.score < MIN_PASSWORD_SCORE) {
    const feedbackMessage = strength.feedback.warning || (strength.feedback.suggestions && strength.feedback.suggestions[0]);
    return { isValid: false, feedback: `Password is too weak. ${feedbackMessage || 'Please choose a stronger password.'}`.trim() };
  }

  return { isValid: true };
};

// Helper for consistent required string validation (handles missing/null/empty)
const requiredString = (message: string) => z.preprocess((val) => val ?? '', z.string().min(1, message));

// Conditionally disable rate limiting for the test environment
const isTestEnv = process.env.NODE_ENV === 'test';

// --- Rate Limiting Configuration ---
const forgotPasswordLimiter = rateLimit({
	windowMs: 15 * 60 * 1000, // 15 minutes
	max: 5,
	message: 'Too many password reset requests from this IP, please try again after 15 minutes.',
	standardHeaders: true,
	legacyHeaders: false,
  skip: () => isTestEnv, // Skip this middleware if in test environment
});

const resetPasswordLimiter = rateLimit({
	windowMs: 15 * 60 * 1000, // 15 minutes
	max: 10,
	message: 'Too many password reset attempts from this IP, please try again after 15 minutes.',
	standardHeaders: true,
	legacyHeaders: false,
  skip: () => isTestEnv, // Skip this middleware if in test environment
});

// --- Zod Schemas for Auth Routes (as per ADR-003) ---

const registerSchema = z.object({
  body: z.object({
    email: z.string().email('A valid email is required.'),
    password: z.string().min(8, 'Password must be at least 8 characters long.').superRefine((password, ctx) => {
      const strength = validatePasswordStrength(password);
      if (!strength.isValid) ctx.addIssue({ code: 'custom', message: strength.feedback });
    }),
    full_name: z.string().optional(),
    avatar_url: z.string().url().optional(),
  }),
});

const forgotPasswordSchema = z.object({
  body: z.object({ email: z.string().email('A valid email is required.') }),
});

const resetPasswordSchema = z.object({
  body: z.object({
    token: requiredString('Token is required.'),
    newPassword: z.string().min(8, 'Password must be at least 8 characters long.').superRefine((password, ctx) => {
      const strength = validatePasswordStrength(password);
      if (!strength.isValid) ctx.addIssue({ code: 'custom', message: strength.feedback });
    }),
  }),
});

// --- Authentication Routes ---

// Registration Route
router.post('/register', validateRequest(registerSchema), async (req: Request, res: Response, next: NextFunction) => {
  type RegisterRequest = z.infer<typeof registerSchema>;
  const { body: { email, password, full_name, avatar_url } } = req as unknown as RegisterRequest;
  
  try {
    const saltRounds = 10;
    const hashedPassword = await bcrypt.hash(password, saltRounds);
    logger.info(`Hashing password for new user: ${email}`);

    // The createUser method in UserRepository now handles its own transaction.
    const newUser = await userRepo.createUser(email, hashedPassword, { full_name, avatar_url }, req.log);

    const userEmail = newUser.user.email || 'unknown';
    const userId = newUser.user_id || 'unknown';
    logger.info(`Successfully created new user in DB: ${userEmail} (ID: ${userId})`);

    // Use the new standardized logging function
    await adminRepo.logActivity({
      userId: newUser.user_id,
      action: 'user_registered',
      displayText: `${userEmail} has registered.`,
      icon: 'user-plus',
    }, req.log);

    const payload = { user_id: newUser.user_id, email: userEmail };
    const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '1h' });

    const refreshToken = crypto.randomBytes(64).toString('hex');
    await userRepo.saveRefreshToken(newUser.user_id, refreshToken, req.log);

    res.cookie('refreshToken', refreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    });
    return res.status(201).json({ message: 'User registered successfully!', user: payload, token });
  } catch (error: unknown) {
    if (error instanceof UniqueConstraintError) {
      // If the email is a duplicate, return a 409 Conflict status.
      return res.status(409).json({ message: error.message });
    }
    // The createUser method now handles its own transaction logging, so we just log the route failure.
    logger.error({ error }, `User registration route failed for email: ${email}.`);
    return next(error);
  }
});

// Login Route
router.post('/login', (req: Request, res: Response, next: NextFunction) => {
  passport.authenticate('local', { session: false }, async (err: Error, user: Express.User | false, info: { message: string }) => {
    // --- LOGIN ROUTE DEBUG LOGGING ---
    req.log.debug(`[API /login] Received login request for email: ${req.body.email}`);
    if (err) req.log.error({ err }, '[API /login] Passport reported an error.');
    if (!user) req.log.warn({ info }, '[API /login] Passport reported NO USER found.');
    if (user) req.log.debug({ user }, '[API /login] Passport user object:'); // Log the user object passport returns
    if (user) req.log.info({ user }, '[API /login] Passport reported USER FOUND.');

    try {
        const allUsersInDb = await getPool().query('SELECT u.user_id, u.email, p.role FROM public.users u JOIN public.profiles p ON u.user_id = p.user_id');
        req.log.debug('[API /login] Current users in DB from SERVER perspective:');
        console.table(allUsersInDb.rows);
    } catch (dbError) {
        req.log.error({ dbError }, '[API /login] Could not query users table for debugging.');
    }
    // --- END DEBUG LOGGING ---
    const { rememberMe } = req.body;
    if (err) {
      req.log.error({ error: err }, `Login authentication error in /login route for email: ${req.body.email}`);
      return next(err);
    }
    if (!user) {
      return res.status(401).json({ message: info.message || 'Login failed' });
    }

    const typedUser = user as UserProfile;
    const payload = { user_id: typedUser.user.user_id, email: typedUser.user.email };
    const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });

    try {
      const refreshToken = crypto.randomBytes(64).toString('hex'); // This was a duplicate, fixed.
      await userRepo.saveRefreshToken(typedUser.user.user_id, refreshToken, req.log);
      req.log.info(`JWT and refresh token issued for user: ${typedUser.user.email}`);
      
      const cookieOptions = {
          httpOnly: true,
          secure: process.env.NODE_ENV === 'production',
          maxAge: rememberMe ? 30 * 24 * 60 * 60 * 1000 : undefined
      };

      res.cookie('refreshToken', refreshToken, cookieOptions);
      const userResponse = { user_id: typedUser.user.user_id, email: typedUser.user.email };

      return res.json({ user: userResponse, token: accessToken });
    } catch (tokenErr) {
      req.log.error({ error: tokenErr }, `Failed to save refresh token during login for user: ${typedUser.user.email}`);
      return next(tokenErr);
    }
  })(req, res, next);
});

// Route to request a password reset
router.post('/forgot-password', forgotPasswordLimiter, validateRequest(forgotPasswordSchema), async (req: Request, res: Response, next: NextFunction) => {
  type ForgotPasswordRequest = z.infer<typeof forgotPasswordSchema>;
  const { body: { email } } = req as unknown as ForgotPasswordRequest;
  
  try {
    req.log.debug(`[API /forgot-password] Received request for email: ${email}`);
    const user = await userRepo.findUserByEmail(email, req.log);
    let token: string | undefined;
    req.log.debug({ user: user ? { user_id: user.user_id, email: user.email } : 'NOT FOUND' }, `[API /forgot-password] Database search result for ${email}:`);

    if (user) {
      token = crypto.randomBytes(32).toString('hex');
      const saltRounds = 10;
      const tokenHash = await bcrypt.hash(token, saltRounds);
      const expiresAt = new Date(Date.now() + 3600000); // 1 hour

      await userRepo.createPasswordResetToken(user.user_id, tokenHash, expiresAt, req.log);

      const resetLink = `${process.env.FRONTEND_URL}/reset-password/${token}`;

      try {
        await sendPasswordResetEmail(email, resetLink, req.log);
      } catch (emailError) {
        req.log.error({ emailError }, `Email send failure during password reset for user`);
      }
    } else {
      req.log.warn(`Password reset requested for non-existent email: ${email}`);
    }

    // For testability, return the token in the response only in the test environment.
    const responsePayload: { message: string; token?: string } = {
      message: 'If an account with that email exists, a password reset link has been sent.',
    };
    if (process.env.NODE_ENV === 'test' && user) responsePayload.token = token;
    res.status(200).json(responsePayload);
  } catch (error) {
    req.log.error({ error }, `An error occurred during /forgot-password for email: ${email}`);
    next(error);
  }
});

// Route to reset the password using a token
router.post('/reset-password', resetPasswordLimiter, validateRequest(resetPasswordSchema), async (req: Request, res: Response, next: NextFunction) => {
  type ResetPasswordRequest = z.infer<typeof resetPasswordSchema>;
  const { body: { token, newPassword } } = req as unknown as ResetPasswordRequest;
  
  try {
    const validTokens = await userRepo.getValidResetTokens(req.log);
    let tokenRecord;
    for (const record of validTokens) {
        const isMatch = await bcrypt.compare(token, record.token_hash);
        if (isMatch) {
            tokenRecord = record;
            break;
        }
    }

    if (!tokenRecord) {
      return res.status(400).json({ message: 'Invalid or expired password reset token.' });
    }

    const saltRounds = 10;
    const hashedPassword = await bcrypt.hash(newPassword, saltRounds);

    await userRepo.updateUserPassword(tokenRecord.user_id, hashedPassword, req.log);
    await userRepo.deleteResetToken(tokenRecord.token_hash, req.log);

    // Log this security event after a successful password reset.
    await adminRepo.logActivity({
      userId: tokenRecord.user_id,
      action: 'password_reset',
      displayText: `User ID ${tokenRecord.user_id} has reset their password.`,
      icon: 'key',
      details: { source_ip: req.ip ?? null }
    }, req.log);

    res.status(200).json({ message: 'Password has been reset successfully.' });
  } catch (error) {
    req.log.error({ error }, `An error occurred during password reset.`);
    next(error);
  }
});

// New Route to refresh the access token
router.post('/refresh-token', async (req: Request, res: Response, next: NextFunction) => {
    const { refreshToken } = req.cookies;
    if (!refreshToken) {
      return res.status(401).json({ message: 'Refresh token not found.' });
    }

    try {
      const user = await userRepo.findUserByRefreshToken(refreshToken, req.log);
      if (!user) {
        return res.status(403).json({ message: 'Invalid or expired refresh token.' });
      }

      const payload = { user_id: user.user_id, email: user.email };
      const newAccessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });

      res.json({ token: newAccessToken });
    } catch (error) {
      req.log.error({ error }, 'An error occurred during /refresh-token.');
      next(error);
    }
});

/**
 * POST /api/auth/logout - Logs the user out by invalidating their refresh token.
 * It clears the refresh token from the database and instructs the client to
 * expire the `refreshToken` cookie.
 */
router.post('/logout', async (req: Request, res: Response) => {
    const { refreshToken } = req.cookies;
    if (refreshToken) {
        // Invalidate the token in the database so it cannot be used again.
        // We don't need to wait for this to finish to respond to the user.
        userRepo.deleteRefreshToken(refreshToken, req.log).catch((err: Error) => {
            req.log.error({ error: err }, 'Failed to delete refresh token from DB during logout.');
        });
    }
    // Instruct the browser to clear the cookie by setting its expiration to the past.
    res.cookie('refreshToken', '', { httpOnly: true, expires: new Date(0), secure: process.env.NODE_ENV === 'production' });
    res.status(200).json({ message: 'Logged out successfully.' });
});

// --- OAuth Routes ---

// const handleOAuthCallback = (req: Request, res: Response) => {
//     const user = req.user as { user_id: string; email: string };
//     const payload = { user_id: user.user_id, email: user.email };
//     const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
//     const refreshToken = crypto.randomBytes(64).toString('hex');

//     db.saveRefreshToken(user.user_id, refreshToken).then(() => {
//         res.cookie('refreshToken', refreshToken, {
//             httpOnly: true,
//             secure: process.env.NODE_ENV === 'production',
//             maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
//         });
//         // Redirect to a frontend page that can handle the token
//         res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${accessToken}`);
//     }).catch(err => {
//         logger.error('Failed to save refresh token during OAuth callback:', { error: err });
//         res.redirect(`${process.env.FRONTEND_URL}/login?error=auth_failed`);
//     });
// };

// router.get('/google', passport.authenticate('google', { session: false }));
// router.get('/google/callback', passport.authenticate('google', { session: false, failureRedirect: '/login' }), handleOAuthCallback);

// router.get('/github', passport.authenticate('github', { session: false }));
// router.get('/github/callback', passport.authenticate('github', { session: false, failureRedirect: '/login' }), handleOAuthCallback);

export default router;