// src/routes/auth.routes.ts
import { Router, Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import rateLimit from 'express-rate-limit';
import passport from './passport.routes';
import { UniqueConstraintError } from '../services/db/errors.db'; // Import actual class for instanceof checks
import { logger } from '../services/logger.server';
import { validateRequest } from '../middleware/validation.middleware';
import type { UserProfile } from '../types';
import { validatePasswordStrength } from '../utils/authUtils';
import { requiredString } from '../utils/zodUtils';

import { authService } from '../services/authService';
const router = Router();

// 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,
  // Do not skip in test environment so we can write integration tests for it.
  // The limiter uses an in-memory store by default, so counts are reset when the test server restarts.
  // skip: () => isTestEnv,
});

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
});

const registerSchema = z.object({
  body: z.object({
    // Sanitize email by trimming and converting to lowercase.
    email: z.string().trim().toLowerCase().email('A valid email is required.'),
    password: z
      .string()
      .trim() // Prevent leading/trailing whitespace in passwords.
      .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 });
      }),
    // Sanitize optional string inputs.
    full_name: z.string().trim().optional(),
    // Allow empty string or valid URL. If empty string is received, convert to undefined.
    avatar_url: z.preprocess(
      (val) => (val === '' ? undefined : val),
      z.string().trim().url().optional(),
    ),
  }),
});

const forgotPasswordSchema = z.object({
  body: z.object({
    // Sanitize email by trimming and converting to lowercase.
    email: z.string().trim().toLowerCase().email('A valid email is required.'),
  }),
});

const resetPasswordSchema = z.object({
  body: z.object({
    token: requiredString('Token is required.'),
    newPassword: z
      .string()
      .trim() // Prevent leading/trailing whitespace in passwords.
      .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 { newUserProfile, accessToken, refreshToken } = await authService.registerAndLoginUser(
        email,
        password,
        full_name,
        avatar_url,
        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!', userprofile: newUserProfile, token: accessToken });
    } 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 });
      }
      logger.error({ error }, `User registration route failed for email: ${email}.`);
      // Pass the error to the centralized handler
      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.');

      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' });
      }

      try {
        const { rememberMe } = req.body;
        const userProfile = user as UserProfile;
        const { accessToken, refreshToken } = await authService.handleSuccessfulLogin(userProfile, req.log);
        req.log.info(`JWT and refresh token issued for user: ${userProfile.user.email}`);

        const cookieOptions = {
          httpOnly: true,
          secure: process.env.NODE_ENV === 'production',
          maxAge: rememberMe ? 30 * 24 * 60 * 60 * 1000 : undefined, // 30 days
        };

        res.cookie('refreshToken', refreshToken, cookieOptions);
        // Return the full user profile object on login to avoid a second fetch on the client.
        return res.json({ userprofile: userProfile, token: accessToken });
      } catch (tokenErr) {
        const email = (user as UserProfile)?.user?.email || req.body.email;
        req.log.error({ error: tokenErr }, `Failed to process login for 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 {
      // The service handles finding the user, creating the token, and sending the email.
      const token = await authService.resetPassword(email, req.log);

      // 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' && token) 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 resetSuccessful = await authService.updatePassword(token, newPassword, req.log);

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

      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 result = await authService.refreshAccessToken(refreshToken, req.log);
    if (!result) {
      return res.status(403).json({ message: 'Invalid or expired refresh token.' });
    }
    res.json({ token: result.accessToken });
  } 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.
    authService.logout(refreshToken, req.log).catch((err: Error) => {
      req.log.error({ error: err }, 'Logout token invalidation failed in background.');
    });
  }
  // Instruct the browser to clear the cookie by setting its expiration to the past.
  res.cookie('refreshToken', '', {
    httpOnly: true,
    maxAge: 0, // Use maxAge for modern compatibility; Express sets 'Expires' as a fallback.
    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;
