// src/routes/auth.routes.ts
// All route handlers now use req.log (request-scoped logger) as per ADR-004
import { Router, Request, Response, NextFunction } from 'express';
// All route handlers now use req.log (request-scoped logger) as per ADR-004
import { z } from 'zod';
import passport from '../config/passport';
// All route handlers now use req.log (request-scoped logger) as per ADR-004
import { UniqueConstraintError } from '../services/db/errors.db'; // Import actual class for instanceof checks
// Removed: import { logger } from '../services/logger.server';
// All route handlers now use req.log (request-scoped logger) as per ADR-004
import { validateRequest } from '../middleware/validation.middleware';
import type { UserProfile } from '../types';
// All route handlers now use req.log (request-scoped logger) as per ADR-004
import { validatePasswordStrength } from '../utils/authUtils';
// All route handlers now use req.log (request-scoped logger) as per ADR-004
import { requiredString } from '../utils/zodUtils';
// All route handlers now use req.log (request-scoped logger) as per ADR-004
import {
  loginLimiter,
  registerLimiter,
  forgotPasswordLimiter,
  resetPasswordLimiter,
  refreshTokenLimiter,
  logoutLimiter,
} from '../config/rateLimiters';
import { sendSuccess, sendError, ErrorCode } from '../utils/apiResponse';

// All route handlers now use req.log (request-scoped logger) as per ADR-004
import { authService } from '../services/authService';
const router = Router();

// --- Reusable Schemas ---

const passwordSchema = 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 });
  });

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: passwordSchema,
    // Sanitize optional string inputs.
    full_name: z.preprocess((val) => (val === '' ? undefined : val), 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 loginSchema = z.object({
  body: z.object({
    email: z.string().trim().toLowerCase().email('A valid email is required.'),
    password: requiredString('Password is required.'),
    rememberMe: z.boolean().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: passwordSchema,
  }),
});

// --- Authentication Routes ---

/**
 * @openapi
 * /auth/register:
 *   post:
 *     summary: Register a new user
 *     description: Creates a new user account and returns authentication tokens.
 *     tags:
 *       - Auth
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *               - password
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *                 example: user@example.com
 *               password:
 *                 type: string
 *                 format: password
 *                 minLength: 8
 *                 description: Must be at least 8 characters with good entropy
 *               full_name:
 *                 type: string
 *                 example: John Doe
 *               avatar_url:
 *                 type: string
 *                 format: uri
 *     responses:
 *       201:
 *         description: User registered successfully
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     message:
 *                       type: string
 *                       example: User registered successfully!
 *                     userprofile:
 *                       type: object
 *                     token:
 *                       type: string
 *                       description: JWT access token
 *       409:
 *         description: Email already registered
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ErrorResponse'
 */
router.post(
  '/register',
  registerLimiter,
  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 sendSuccess(
        res,
        {
          message: 'User registered successfully!',
          userprofile: newUserProfile,
          token: accessToken,
        },
        201,
      );
    } catch (error: unknown) {
      if (error instanceof UniqueConstraintError) {
        // If the email is a duplicate, return a 409 Conflict status.
        return sendError(res, ErrorCode.CONFLICT, error.message, 409);
      }
      req.log.error({ error }, `User registration route failed for email: ${email}.`);
      // Pass the error to the centralized handler
      return next(error);
    }
  },
);

/**
 * @openapi
 * /auth/login:
 *   post:
 *     summary: Login with email and password
 *     description: Authenticates user credentials and returns JWT tokens.
 *     tags:
 *       - Auth
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *               - password
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *                 example: user@example.com
 *               password:
 *                 type: string
 *                 format: password
 *               rememberMe:
 *                 type: boolean
 *                 description: If true, refresh token lasts 30 days
 *     responses:
 *       200:
 *         description: Login successful
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     userprofile:
 *                       type: object
 *                     token:
 *                       type: string
 *                       description: JWT access token
 *       401:
 *         description: Invalid credentials
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ErrorResponse'
 */
router.post(
  '/login',
  loginLimiter,
  validateRequest(loginSchema),
  (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 sendError(res, ErrorCode.UNAUTHORIZED, info.message || 'Login failed', 401);
        }

        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 sendSuccess(res, { 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);
  },
);

/**
 * @openapi
 * /auth/forgot-password:
 *   post:
 *     summary: Request password reset
 *     description: Sends a password reset email if the account exists. Always returns success to prevent email enumeration.
 *     tags:
 *       - Auth
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *                 example: user@example.com
 *     responses:
 *       200:
 *         description: Request processed (email sent if account exists)
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     message:
 *                       type: string
 *                       example: If an account with that email exists, a password reset link has been sent.
 */
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;
      sendSuccess(res, responsePayload);
    } catch (error) {
      req.log.error({ error }, `An error occurred during /forgot-password for email: ${email}`);
      next(error);
    }
  },
);

/**
 * @openapi
 * /auth/reset-password:
 *   post:
 *     summary: Reset password with token
 *     description: Resets the user's password using a valid reset token from the forgot-password email.
 *     tags:
 *       - Auth
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - token
 *               - newPassword
 *             properties:
 *               token:
 *                 type: string
 *                 description: Password reset token from email
 *               newPassword:
 *                 type: string
 *                 format: password
 *                 minLength: 8
 *     responses:
 *       200:
 *         description: Password reset successful
 *       400:
 *         description: Invalid or expired token
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ErrorResponse'
 */
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 sendError(
          res,
          ErrorCode.BAD_REQUEST,
          'Invalid or expired password reset token.',
          400,
        );
      }

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

/**
 * @openapi
 * /auth/refresh-token:
 *   post:
 *     summary: Refresh access token
 *     description: Uses the refresh token cookie to issue a new access token.
 *     tags:
 *       - Auth
 *     responses:
 *       200:
 *         description: New access token issued
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     token:
 *                       type: string
 *                       description: New JWT access token
 *       401:
 *         description: Refresh token not found
 *       403:
 *         description: Invalid or expired refresh token
 */
router.post(
  '/refresh-token',
  refreshTokenLimiter,
  async (req: Request, res: Response, next: NextFunction) => {
    const { refreshToken } = req.cookies;
    if (!refreshToken) {
      return sendError(res, ErrorCode.UNAUTHORIZED, 'Refresh token not found.', 401);
    }

    try {
      const result = await authService.refreshAccessToken(refreshToken, req.log);
      if (!result) {
        return sendError(res, ErrorCode.FORBIDDEN, 'Invalid or expired refresh token.', 403);
      }
      sendSuccess(res, { token: result.accessToken });
    } catch (error) {
      req.log.error({ error }, 'An error occurred during /refresh-token.');
      next(error);
    }
  },
);

/**
 * @openapi
 * /auth/logout:
 *   post:
 *     summary: Logout user
 *     description: Invalidates the refresh token and clears the cookie.
 *     tags:
 *       - Auth
 *     responses:
 *       200:
 *         description: Logged out successfully
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 data:
 *                   type: object
 *                   properties:
 *                     message:
 *                       type: string
 *                       example: Logged out successfully.
 */
router.post('/logout', logoutLimiter, 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',
  });
  sendSuccess(res, { message: 'Logged out successfully.' });
});

// --- OAuth Routes ---

/**
 * @openapi
 * /auth/google:
 *   get:
 *     summary: Initiate Google OAuth
 *     description: Redirects to Google for authentication. After success, redirects back to the app with a token.
 *     tags:
 *       - Auth
 *     responses:
 *       302:
 *         description: Redirects to Google OAuth consent screen
 *
 * /auth/github:
 *   get:
 *     summary: Initiate GitHub OAuth
 *     description: Redirects to GitHub for authentication. After success, redirects back to the app with a token.
 *     tags:
 *       - Auth
 *     responses:
 *       302:
 *         description: Redirects to GitHub OAuth consent screen
 */

/**
 * Handles the OAuth callback after successful authentication.
 * Generates tokens and redirects to the frontend with the access token.
 * @param provider The OAuth provider name ('google' or 'github') for the query param.
 */
const createOAuthCallbackHandler = (provider: 'google' | 'github') => {
  return async (req: Request, res: Response) => {
    const userProfile = req.user as UserProfile;

    if (!userProfile || !userProfile.user) {
      req.log.error('OAuth callback received but no user profile found');
      return res.redirect(`${process.env.FRONTEND_URL}/?error=auth_failed`);
    }

    try {
      const { accessToken, refreshToken } = await authService.handleSuccessfulLogin(
        userProfile,
        req.log,
      );

      res.cookie('refreshToken', refreshToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
      });

      // Redirect to frontend with the token in a provider-specific query param
      // The frontend useAppInitialization hook looks for googleAuthToken or githubAuthToken
      const tokenParam = provider === 'google' ? 'googleAuthToken' : 'githubAuthToken';
      res.redirect(`${process.env.FRONTEND_URL}/?${tokenParam}=${accessToken}`);
    } catch (err) {
      req.log.error({ error: err }, `Failed to complete ${provider} OAuth login`);
      res.redirect(`${process.env.FRONTEND_URL}/?error=auth_failed`);
    }
  };
};

/* istanbul ignore next -- @preserve: OAuth routes require external provider interaction, not suitable for automated testing */
// Google OAuth routes
router.get('/google', passport.authenticate('google', { session: false }));
router.get(
  '/google/callback',
  passport.authenticate('google', {
    session: false,
    failureRedirect: '/?error=google_auth_failed',
  }),
  createOAuthCallbackHandler('google'),
);

/* istanbul ignore next -- @preserve: OAuth routes require external provider interaction, not suitable for automated testing */
// GitHub OAuth routes
router.get('/github', passport.authenticate('github', { session: false }));
router.get(
  '/github/callback',
  passport.authenticate('github', {
    session: false,
    failureRedirect: '/?error=github_auth_failed',
  }),
  createOAuthCallbackHandler('github'),
);

export default router;
