// src/routes/passport.routes.ts
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';
//import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
//import { Strategy as GitHubStrategy } from 'passport-github2';
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import * as bcrypt from 'bcrypt';
import { Request, Response, NextFunction } from 'express';

import * as db from '../services/db/index.db';
import { logger } from '../services/logger.server';
import { UserProfile } from '../types';
import { createMockUserProfile } from '../tests/utils/mockFactories';

const JWT_SECRET = process.env.JWT_SECRET!;

const MAX_FAILED_ATTEMPTS = 5;
const LOCKOUT_DURATION_MINUTES = 15;

/**
 * A type guard to check if an object is a UserProfile.
 * This is useful for safely accessing properties on `req.user`.
 * @param user The user object to check.
 * @returns True if the object is a UserProfile, false otherwise.
 */
function isUserProfile(user: unknown): user is UserProfile {
  return typeof user === 'object' && user !== null && 'user_id' in user && 'role' in user;
}

// --- Passport Local Strategy (for email/password login) ---
passport.use(new LocalStrategy(
  { 
    usernameField: 'email',
    passReqToCallback: true // Pass the request object to the callback
  },
  async (req: Request, email, password, done) => {
    try {
      // 1. Find the user by email, including their profile data for the JWT payload.
      const user = await db.userRepo.findUserWithProfileByEmail(email, req.log);

      if (!user) {
        // User not found
        logger.warn(`Login attempt failed for non-existent user: ${email}`);
        return done(null, false, { message: 'Incorrect email or password.' });
      }

      // Check if the account is currently locked.
      if (user.failed_login_attempts >= MAX_FAILED_ATTEMPTS && user.last_failed_login) {
        const lockoutTime = new Date(user.last_failed_login).getTime();
        const timeSinceLockout = Date.now() - lockoutTime;
        const lockoutDurationMs = LOCKOUT_DURATION_MINUTES * 60 * 1000;

        if (timeSinceLockout < lockoutDurationMs) {
          logger.warn(`Login attempt for locked account: ${email}`);
          return done(null, false, { message: `Account is temporarily locked. Please try again in ${LOCKOUT_DURATION_MINUTES} minutes.` });
        }
      }

      if (!user.password_hash) {
        // User exists but signed up via OAuth, so they don't have a password.
        logger.warn(`Password login attempt for OAuth user: ${email}`);
        return done(null, false, { message: 'This account was created using a social login. Please use Google or GitHub to sign in.' });
      }

      // 2. Compare the submitted password with the hashed password in your DB.
      logger.debug(`[Passport] Verifying password for ${email}. Hash length: ${user.password_hash.length}`);
      const isMatch = await bcrypt.compare(password, user.password_hash);
      logger.debug(`[Passport] Password match result: ${isMatch}`);
      
      if (!isMatch) {
        // Password does not match
        logger.warn(`Login attempt failed for user ${email} due to incorrect password.`);

        // Increment failed attempts
        await db.adminRepo.incrementFailedLoginAttempts(user.user_id, req.log);
        
        // Log this security event.
        await db.adminRepo.logActivity({
          userId: user.user_id,
          action: 'login_failed_password',
          displayText: `Failed login attempt for user ${user.email}.`,
          icon: 'shield-alert',
          details: { source_ip: req.ip ?? null },
        }, req.log);
        return done(null, false, { message: 'Incorrect email or password.' });
      }

      // 3. Success! Return the user object (without password_hash for security).
      // Reset failed login attempts upon successful login.
      await db.adminRepo.resetFailedLoginAttempts(user.user_id, req.ip ?? 'unknown', req.log);

      logger.info(`User successfully authenticated: ${email}`);
      // The user object from `findUserWithProfileByEmail` already excludes the password hash.
      // This object will be passed to the /login route handler.
      const userWithoutHash = user;
      return done(null, userWithoutHash);
    } catch (err: unknown) {
      req.log.error({ error: err }, 'Error during local authentication strategy:');
      return done(err);
    }
  }
));

// --- Passport Google OAuth 2.0 Strategy ---
// passport.use(new GoogleStrategy({
//     clientID: process.env.GOOGLE_CLIENT_ID!,
//     clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
//     callbackURL: '/api/auth/google/callback', // Must match the one in Google Cloud Console
//     scope: ['profile', 'email']
//   },
//   async (accessToken, refreshToken, profile, done) => {
//     try {
//       const email = profile.emails?.[0]?.value;
//       if (!email) {
//         return done(new Error("No email found in Google profile."), false);
//       }

//       // Check if user already exists in our database
//       const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned

//       if (user) {
//         // User exists, proceed to log them in.
//         logger.info(`Google OAuth successful for existing user: ${email}`);
//         // The password_hash is intentionally destructured and discarded for security.
//         const { password_hash, ...userWithoutHash } = user; 
//         return done(null, userWithoutHash);
//       } else {
//         // User does not exist, create a new account for them.
//         logger.info(`Google OAuth: creating new user for email: ${email}`);
        
//         // Since this is an OAuth user, they don't have a password.
//         // We pass `null` for the password hash.
//         const newUser = await db.createUser(email, null, {
//           full_name: profile.displayName,
//           avatar_url: profile.photos?.[0]?.value
//         });

//         // Send a welcome email to the new user
//         try {
//             await sendWelcomeEmail(email, profile.displayName);
//         } catch (emailError) {
//             logger.error(`Failed to send welcome email to new Google user ${email}`, { error: emailError });
//             // Don't block the login flow if email fails.
//         }

//         // The `createUser` function returns the user object without the password hash.
//         return done(null, newUser);
//       }
//     } catch (err) {
//       logger.error('Error during Google authentication strategy:', { error: err });
//       return done(err, false);
//     }
//   }
// ));

// --- Passport GitHub OAuth 2.0 Strategy ---
// passport.use(new GitHubStrategy({
//     clientID: process.env.GITHUB_CLIENT_ID!,
//     clientSecret: process.env.GITHUB_CLIENT_SECRET!,
//     callbackURL: '/api/auth/github/callback', // Must match the one in GitHub OAuth App settings
//     scope: ['user:email'] // Request email access
//   },
//   async (accessToken, refreshToken, profile, done) => {
//     try {
//       const email = profile.emails?.[0]?.value;
//       if (!email) {
//         return done(new Error("No public email found in GitHub profile. Please ensure your primary email is public or add one."), false);
//       }

//       // Check if user already exists in our database
//       const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned

//       if (user) {
//         // User exists, proceed to log them in.
//         logger.info(`GitHub OAuth successful for existing user: ${email}`);
//         // The password_hash is intentionally destructured and discarded for security.
//         const { password_hash, ...userWithoutHash } = user; 
//         return done(null, userWithoutHash);
//       } else {
//         // User does not exist, create a new account for them.
//         logger.info(`GitHub OAuth: creating new user for email: ${email}`);
        
//         // Since this is an OAuth user, they don't have a password.
//         // We pass `null` for the password hash.
//         const newUser = await db.createUser(email, null, {
//           full_name: profile.displayName || profile.username, // GitHub profile might not have displayName
//           avatar_url: profile.photos?.[0]?.value
//         });

//         // Send a welcome email to the new user
//         try {
//             await sendWelcomeEmail(email, profile.displayName || profile.username);
//         } catch (emailError) {
//             logger.error(`Failed to send welcome email to new GitHub user ${email}`, { error: emailError });
//             // Don't block the login flow if email fails.
//         }

//         // The `createUser` function returns the user object without the password hash.
//         return done(null, newUser);
//       }
//     } catch (err) {
//       logger.error('Error during GitHub authentication strategy:', { error: err });
//       return done(err, false);
//     }
//   }
// ));

// --- Passport JWT Strategy (for protecting API routes) ---
const jwtOptions = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Expect JWT in 'Authorization: Bearer <token>' header
  secretOrKey: JWT_SECRET,
};

passport.use(new JwtStrategy(jwtOptions, async (jwt_payload, done) => {
  logger.debug({ jwt_payload: jwt_payload ? { user_id: jwt_payload.user_id } : 'null' }, '[JWT Strategy] Verifying token payload:');
  try {
    // The jwt_payload contains the data you put into the token during login (e.g., { user_id: user.user_id, email: user.email }).
    // We re-fetch the user from the database here to ensure they are still active and valid.
    const userProfile = await db.userRepo.findUserProfileById(jwt_payload.user_id, logger);

    // --- JWT STRATEGY DEBUG LOGGING ---
    logger.debug(`[JWT Strategy] DB lookup for user ID ${jwt_payload.user_id} result: ${userProfile ? 'FOUND' : 'NOT FOUND'}`);

    if (userProfile) {
      return done(null, userProfile); // User profile object will be available as req.user in protected routes
    } else {
      logger.warn(`JWT authentication failed: user with ID ${jwt_payload.user_id} not found.`);
      return done(null, false); // User not found or invalid token
    }
  } catch (err: unknown) {
    logger.error({ error: err }, 'Error during JWT authentication strategy:');
    return done(err, false);
  }
}));

// --- Middleware for Admin Role Check ---
export const isAdmin = (req: Request, res: Response, next: NextFunction) => {
    // Use the type guard for safer access to req.user
    const userProfile = req.user;

    if (isUserProfile(userProfile) && userProfile.role === 'admin') {
        next();
    } else {
        // Check if userProfile is a valid UserProfile before accessing its properties for logging.
        const userIdForLog = isUserProfile(userProfile) ? userProfile.user_id : 'unknown';
        logger.warn(`Admin access denied for user: ${userIdForLog}`);
        res.status(403).json({ message: 'Forbidden: Administrator access required.' });
    }
};

/**
 * A flexible authentication middleware. It attempts to authenticate via JWT but does NOT
 * reject the request if authentication fails. This allows routes to handle both
 * authenticated and anonymous users. If a valid token is present, `req.user` will be
 * populated; otherwise, it will be undefined, and the request proceeds.
 */
export const optionalAuth = (req: Request, res: Response, next: NextFunction) => {
    // The custom callback for passport.authenticate gives us access to `err`, `user`, and `info`.
    passport.authenticate('jwt', { session: false }, (err: Error | null, user: Express.User | false, info: { message: string } | Error) => {
        // If there's an authentication error (e.g., malformed token), log it but don't block the request.
        if (info) { // The patch requested this specific error handling.
            logger.info({ info: info.message || info.toString() }, 'Optional auth info:');
        } // The patch requested this specific error handling.
        if (user) (req as Express.Request).user = user; // Attach user if authentication succeeds
        
        next(); // Always proceed to the next middleware
    })(req, res, next);
};

/**
 * Mock Authentication Middleware for Testing
 *
 * This middleware is ONLY active when `process.env.NODE_ENV` is 'test'.
 * It bypasses the entire JWT authentication flow and directly injects a
 * mock user object into `req.user`. This is essential for integration tests,
 * allowing protected routes to be tested without needing to generate valid JWTs
 * or mock the passport strategy.
 *
 * In any environment other than 'test', it does nothing and immediately passes
 * control to the next middleware.
 */
export const mockAuth = (req: Request, res: Response, next: NextFunction) => {
    if (process.env.NODE_ENV === 'test') {
        // In a test environment, attach a mock user to the request.
        // We use the mock factory to create a consistent, type-safe user profile.
        // We override the default role to 'admin' for broad access in tests.
        req.user = createMockUserProfile({
            role: 'admin',
        });
    }
    // In production or development, this middleware does nothing.
    next();
};

export default passport;