const request = require('supertest');
const express = require('express');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { OAuth2Client } = require('google-auth-library');

// Mock dependencies
jest.mock('jsonwebtoken');
jest.mock('google-auth-library');
jest.mock('sequelize', () => ({
  Op: {
    or: 'or'
  }
}));

jest.mock('../../../models', () => ({
  User: {
    findOne: jest.fn(),
    create: jest.fn(),
    findByPk: jest.fn()
  },
  AlphaInvitation: {
    findOne: jest.fn()
  }
}));

// Mock middleware
jest.mock('../../../middleware/validation', () => ({
  sanitizeInput: (req, res, next) => next(),
  validateRegistration: (req, res, next) => next(),
  validateLogin: (req, res, next) => next(),
  validateGoogleAuth: (req, res, next) => next(),
  validateForgotPassword: (req, res, next) => next(),
  validateResetPassword: (req, res, next) => next(),
  validateVerifyResetToken: (req, res, next) => next(),
}));

jest.mock('../../../middleware/csrf', () => ({
  csrfProtection: (req, res, next) => next(),
  getCSRFToken: (req, res) => res.json({ csrfToken: 'test-csrf-token' })
}));

jest.mock('../../../middleware/rateLimiter', () => ({
  loginLimiter: (req, res, next) => next(),
  registerLimiter: (req, res, next) => next(),
  passwordResetLimiter: (req, res, next) => next(),
  emailVerificationLimiter: (req, res, next) => next(),
}));

jest.mock('../../../middleware/auth', () => ({
  optionalAuth: (req, res, next) => next(),
  authenticateToken: (req, res, next) => {
    req.user = { id: 'user-123' };
    next();
  },
}));

jest.mock('../../../services/email', () => ({
  auth: {
    sendVerificationEmail: jest.fn().mockResolvedValue(),
    sendPasswordResetEmail: jest.fn().mockResolvedValue(),
    sendPasswordChangedEmail: jest.fn().mockResolvedValue(),
  }
}));

jest.mock('../../../utils/logger', () => ({
  info: jest.fn(),
  error: jest.fn(),
  warn: jest.fn(),
  withRequestId: jest.fn(() => ({
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
  })),
}));

const { User, AlphaInvitation } = require('../../../models');
const emailService = require('../../../services/email');

// Set up OAuth2Client mock before requiring authRoutes
const mockGoogleClient = {
  verifyIdToken: jest.fn(),
  getToken: jest.fn()
};
OAuth2Client.mockImplementation(() => mockGoogleClient);

const authRoutes = require('../../../routes/auth');

const app = express();
app.use(express.json());
app.use(cookieParser());
app.use('/auth', authRoutes);

// Add error handler
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});

describe('Auth Routes', () => {
  beforeEach(() => {
    jest.clearAllMocks();

    // Reset environment
    process.env.JWT_ACCESS_SECRET = 'test-access-secret';
    process.env.JWT_REFRESH_SECRET = 'test-refresh-secret';
    process.env.GOOGLE_CLIENT_ID = 'test-google-client-id';
    process.env.NODE_ENV = 'test';
    delete process.env.ALPHA_TESTING_ENABLED;

    // Reset JWT mock to return different tokens for each call
    let tokenCallCount = 0;
    jwt.sign.mockImplementation(() => {
      tokenCallCount++;
      return tokenCallCount % 2 === 1 ? 'access-token' : 'refresh-token';
    });
  });

  describe('GET /auth/csrf-token', () => {
    it('should return CSRF token', async () => {
      const response = await request(app)
        .get('/auth/csrf-token');

      expect(response.status).toBe(200);
      expect(response.body).toHaveProperty('csrfToken');
      expect(response.body.csrfToken).toBe('test-csrf-token');
    });
  });

  describe('POST /auth/register', () => {
    it('should register a new user successfully', async () => {
      User.findOne.mockResolvedValue(null);

      const newUser = {
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: false,
        jwtVersion: 1,
        role: 'user',
        verificationToken: 'test-verification-token',
        generateVerificationToken: jest.fn().mockResolvedValue()
      };

      User.create.mockResolvedValue(newUser);
      emailService.auth.sendVerificationEmail.mockResolvedValue();

      const response = await request(app)
        .post('/auth/register')
        .send({
          email: 'test@example.com',
          password: 'StrongPass123!',
          firstName: 'Test',
          lastName: 'User',
          phone: '1234567890'
        });

      expect(response.status).toBe(201);
      expect(response.body.user).toMatchObject({
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: false
      });
      expect(response.body.verificationEmailSent).toBe(true);
      expect(newUser.generateVerificationToken).toHaveBeenCalled();
      expect(emailService.auth.sendVerificationEmail).toHaveBeenCalledWith(newUser, newUser.verificationToken);

      // Check that cookies are set
      expect(response.headers['set-cookie']).toEqual(
        expect.arrayContaining([
          expect.stringContaining('accessToken'),
          expect.stringContaining('refreshToken')
        ])
      );
    });

    it('should reject registration with existing email', async () => {
      User.findOne.mockResolvedValue({ id: 1, email: 'test@example.com' });

      const response = await request(app)
        .post('/auth/register')
        .send({
          email: 'test@example.com',
          password: 'StrongPass123!',
          firstName: 'Test',
          lastName: 'User'
        });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Registration failed');
      expect(response.body.details[0].message).toBe('An account with this email already exists');
    });

    it('should handle registration errors', async () => {
      User.findOne.mockResolvedValue(null);
      User.create.mockRejectedValue(new Error('Database error'));

      const response = await request(app)
        .post('/auth/register')
        .send({
          email: 'test@example.com',
          password: 'StrongPass123!',
          firstName: 'Test',
          lastName: 'User'
        });

      expect(response.status).toBe(500);
      expect(response.body.error).toBe('Registration failed. Please try again.');
    });

    it('should continue registration even if verification email fails', async () => {
      User.findOne.mockResolvedValue(null);

      const newUser = {
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: false,
        jwtVersion: 1,
        role: 'user',
        verificationToken: 'test-verification-token',
        generateVerificationToken: jest.fn().mockResolvedValue()
      };

      User.create.mockResolvedValue(newUser);
      emailService.auth.sendVerificationEmail.mockRejectedValue(new Error('Email service down'));

      const response = await request(app)
        .post('/auth/register')
        .send({
          email: 'test@example.com',
          password: 'StrongPass123!',
          firstName: 'Test',
          lastName: 'User'
        });

      expect(response.status).toBe(201);
      expect(response.body.user.id).toBe(1);
      expect(response.body.verificationEmailSent).toBe(false);
    });
  });

  describe('POST /auth/login', () => {
    it('should login user with valid credentials', async () => {
      const mockUser = {
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: true,
        jwtVersion: 1,
        role: 'user',
        isLocked: jest.fn().mockReturnValue(false),
        comparePassword: jest.fn().mockResolvedValue(true),
        resetLoginAttempts: jest.fn().mockResolvedValue()
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'test@example.com',
          password: 'password123'
        });

      expect(response.status).toBe(200);
      expect(response.body.user).toMatchObject({
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User'
      });
      expect(mockUser.resetLoginAttempts).toHaveBeenCalled();
      expect(response.headers['set-cookie']).toEqual(
        expect.arrayContaining([
          expect.stringContaining('accessToken'),
          expect.stringContaining('refreshToken')
        ])
      );
    });

    it('should reject login with invalid email', async () => {
      User.findOne.mockResolvedValue(null);

      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'nonexistent@example.com',
          password: 'password123'
        });

      expect(response.status).toBe(401);
      expect(response.body.error).toBe('Please check your email and password, or create an account.');
    });

    it('should reject login with invalid password', async () => {
      const mockUser = {
        id: 1,
        isLocked: jest.fn().mockReturnValue(false),
        comparePassword: jest.fn().mockResolvedValue(false),
        incLoginAttempts: jest.fn().mockResolvedValue()
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'test@example.com',
          password: 'wrongpassword'
        });

      expect(response.status).toBe(401);
      expect(response.body.error).toBe('Please check your email and password, or create an account.');
      expect(mockUser.incLoginAttempts).toHaveBeenCalled();
    });

    it('should reject login for locked account', async () => {
      const mockUser = {
        id: 1,
        isLocked: jest.fn().mockReturnValue(true)
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'test@example.com',
          password: 'password123'
        });

      expect(response.status).toBe(423);
      expect(response.body.error).toContain('Account is temporarily locked');
    });

    it('should handle login errors', async () => {
      User.findOne.mockRejectedValue(new Error('Database error'));

      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'test@example.com',
          password: 'password123'
        });

      expect(response.status).toBe(500);
      expect(response.body.error).toBe('Login failed. Please try again.');
    });
  });

  describe('POST /auth/google', () => {
    const mockGooglePayload = {
      sub: 'google123',
      email: 'test@gmail.com',
      given_name: 'Test',
      family_name: 'User',
      picture: 'https://example.com/profile.jpg'
    };

    beforeEach(() => {
      mockGoogleClient.getToken.mockResolvedValue({
        tokens: { id_token: 'mock-id-token' }
      });
      mockGoogleClient.verifyIdToken.mockResolvedValue({
        getPayload: () => mockGooglePayload
      });
    });

    it('should handle Google OAuth login for new user', async () => {
      User.findOne
        .mockResolvedValueOnce(null) // No existing Google user
        .mockResolvedValueOnce(null); // No existing email user

      const newUser = {
        id: 1,
        email: 'test@gmail.com',
        firstName: 'Test',
        lastName: 'User',
        imageFilename: 'https://example.com/profile.jpg',
        isVerified: true,
        jwtVersion: 1,
        role: 'user'
      };

      User.create.mockResolvedValue(newUser);

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'valid-auth-code' });

      expect(response.status).toBe(200);
      expect(response.body.user).toMatchObject({
        id: 1,
        email: 'test@gmail.com',
        firstName: 'Test',
        lastName: 'User'
      });
      expect(User.create).toHaveBeenCalledWith(expect.objectContaining({
        email: 'test@gmail.com',
        firstName: 'Test',
        lastName: 'User',
        authProvider: 'google',
        providerId: 'google123',
        isVerified: true
      }));
    });

    it('should handle Google OAuth login for existing user', async () => {
      const existingUser = {
        id: 1,
        email: 'test@gmail.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: true,
        jwtVersion: 1,
        role: 'user'
      };

      User.findOne.mockResolvedValue(existingUser);

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'valid-auth-code' });

      expect(response.status).toBe(200);
      expect(response.body.user).toMatchObject({
        id: 1,
        email: 'test@gmail.com'
      });
    });

    it('should reject when email exists with different auth provider', async () => {
      User.findOne
        .mockResolvedValueOnce(null) // No Google user
        .mockResolvedValueOnce({ id: 1, email: 'test@gmail.com', authProvider: 'local' }); // Existing email user

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'valid-auth-code' });

      expect(response.status).toBe(409);
      expect(response.body.error).toContain('An account with this email already exists');
    });

    it('should reject missing authorization code', async () => {
      const response = await request(app)
        .post('/auth/google')
        .send({});

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Authorization code is required');
    });

    it('should handle invalid authorization code', async () => {
      mockGoogleClient.getToken.mockRejectedValue(new Error('invalid_grant'));

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'invalid-code' });

      expect(response.status).toBe(401);
      expect(response.body.error).toContain('Invalid or expired authorization code');
    });

    it('should handle redirect URI mismatch', async () => {
      mockGoogleClient.getToken.mockRejectedValue(new Error('redirect_uri_mismatch'));

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'some-code' });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('Redirect URI mismatch');
    });

    it('should handle missing email permission', async () => {
      mockGoogleClient.verifyIdToken.mockResolvedValue({
        getPayload: () => ({ sub: 'google123' }) // No email
      });

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'valid-auth-code' });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('Email permission is required');
    });

    it('should generate fallback name from email when not provided', async () => {
      mockGoogleClient.verifyIdToken.mockResolvedValue({
        getPayload: () => ({
          sub: 'google123',
          email: 'john.doe@gmail.com'
          // No given_name or family_name
        })
      });

      User.findOne
        .mockResolvedValueOnce(null)
        .mockResolvedValueOnce(null);

      const newUser = {
        id: 1,
        email: 'john.doe@gmail.com',
        firstName: 'John',
        lastName: 'Doe',
        isVerified: true,
        jwtVersion: 1,
        role: 'user'
      };

      User.create.mockResolvedValue(newUser);

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'valid-auth-code' });

      expect(response.status).toBe(200);
      expect(User.create).toHaveBeenCalledWith(expect.objectContaining({
        firstName: 'John',
        lastName: 'Doe'
      }));
    });

    it('should handle Google auth errors gracefully', async () => {
      mockGoogleClient.getToken.mockRejectedValue(new Error('Unknown error'));

      const response = await request(app)
        .post('/auth/google')
        .send({ code: 'some-code' });

      expect(response.status).toBe(500);
      expect(response.body.error).toBe('Google authentication failed. Please try again.');
    });
  });

  describe('POST /auth/verify-email', () => {
    it('should verify email with valid 6-digit code', async () => {
      const mockUser = {
        id: 'user-123',
        email: 'test@example.com',
        isVerified: false,
        verificationToken: '123456',
        verificationTokenExpiry: new Date(Date.now() + 3600000), // 1 hour from now
        verificationAttempts: 0,
        isVerificationLocked: jest.fn().mockReturnValue(false),
        isVerificationTokenValid: jest.fn().mockReturnValue(true),
        verifyEmail: jest.fn().mockResolvedValue()
      };

      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '123456' });

      expect(response.status).toBe(200);
      expect(response.body.message).toBe('Email verified successfully');
      expect(response.body.user).toMatchObject({
        id: 'user-123',
        email: 'test@example.com',
        isVerified: true
      });
      expect(mockUser.verifyEmail).toHaveBeenCalled();
    });

    it('should reject missing code', async () => {
      const response = await request(app)
        .post('/auth/verify-email')
        .send({});

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Verification code required');
      expect(response.body.code).toBe('CODE_REQUIRED');
    });

    it('should reject invalid code format (not 6 digits)', async () => {
      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '12345' }); // Only 5 digits

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Verification code must be 6 digits');
      expect(response.body.code).toBe('INVALID_CODE_FORMAT');
    });

    it('should reject when user not found', async () => {
      User.findByPk.mockResolvedValue(null);

      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '123456' });

      expect(response.status).toBe(404);
      expect(response.body.error).toBe('User not found');
      expect(response.body.code).toBe('USER_NOT_FOUND');
    });

    it('should reject already verified user', async () => {
      const mockUser = {
        id: 'user-123',
        isVerified: true
      };

      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '123456' });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Email already verified');
      expect(response.body.code).toBe('ALREADY_VERIFIED');
    });

    it('should reject when too many verification attempts', async () => {
      const mockUser = {
        id: 'user-123',
        isVerified: false,
        isVerificationLocked: jest.fn().mockReturnValue(true)
      };

      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '123456' });

      expect(response.status).toBe(429);
      expect(response.body.error).toContain('Too many verification attempts');
      expect(response.body.code).toBe('TOO_MANY_ATTEMPTS');
    });

    it('should reject when no verification code exists', async () => {
      const mockUser = {
        id: 'user-123',
        isVerified: false,
        verificationToken: null,
        isVerificationLocked: jest.fn().mockReturnValue(false)
      };

      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '123456' });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('No verification code found');
      expect(response.body.code).toBe('NO_CODE');
    });

    it('should reject expired verification code', async () => {
      const mockUser = {
        id: 'user-123',
        isVerified: false,
        verificationToken: '123456',
        verificationTokenExpiry: new Date(Date.now() - 3600000), // 1 hour ago (expired)
        isVerificationLocked: jest.fn().mockReturnValue(false)
      };

      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '123456' });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('expired');
      expect(response.body.code).toBe('VERIFICATION_EXPIRED');
    });

    it('should handle verification errors', async () => {
      User.findByPk.mockRejectedValue(new Error('Database error'));

      const response = await request(app)
        .post('/auth/verify-email')
        .send({ code: '123456' });

      expect(response.status).toBe(500);
      expect(response.body.error).toBe('Email verification failed. Please try again.');
    });
  });

  describe('POST /auth/resend-verification', () => {
    it('should resend verification email for authenticated unverified user', async () => {
      const mockUser = {
        id: 1,
        email: 'test@example.com',
        isVerified: false,
        verificationToken: 'new-token',
        generateVerificationToken: jest.fn().mockResolvedValue()
      };

      jwt.verify.mockReturnValue({ id: 1 });
      User.findByPk.mockResolvedValue(mockUser);
      emailService.auth.sendVerificationEmail.mockResolvedValue();

      const response = await request(app)
        .post('/auth/resend-verification')
        .set('Cookie', ['accessToken=valid-token']);

      expect(response.status).toBe(200);
      expect(response.body.message).toBe('Verification email sent successfully');
      expect(mockUser.generateVerificationToken).toHaveBeenCalled();
      expect(emailService.auth.sendVerificationEmail).toHaveBeenCalledWith(mockUser, mockUser.verificationToken);
    });

    it('should reject when no access token provided', async () => {
      const response = await request(app)
        .post('/auth/resend-verification');

      expect(response.status).toBe(401);
      expect(response.body.error).toBe('Authentication required');
      expect(response.body.code).toBe('NO_TOKEN');
    });

    it('should reject expired access token', async () => {
      const error = new Error('jwt expired');
      error.name = 'TokenExpiredError';
      jwt.verify.mockImplementation(() => { throw error; });

      const response = await request(app)
        .post('/auth/resend-verification')
        .set('Cookie', ['accessToken=expired-token']);

      expect(response.status).toBe(401);
      expect(response.body.error).toContain('Session expired');
      expect(response.body.code).toBe('TOKEN_EXPIRED');
    });

    it('should reject when user not found', async () => {
      jwt.verify.mockReturnValue({ id: 999 });
      User.findByPk.mockResolvedValue(null);

      const response = await request(app)
        .post('/auth/resend-verification')
        .set('Cookie', ['accessToken=valid-token']);

      expect(response.status).toBe(404);
      expect(response.body.error).toBe('User not found');
    });

    it('should reject when user already verified', async () => {
      const mockUser = {
        id: 1,
        isVerified: true
      };

      jwt.verify.mockReturnValue({ id: 1 });
      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/resend-verification')
        .set('Cookie', ['accessToken=valid-token']);

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Email already verified');
    });

    it('should handle email service failure', async () => {
      const mockUser = {
        id: 1,
        isVerified: false,
        generateVerificationToken: jest.fn().mockResolvedValue()
      };

      jwt.verify.mockReturnValue({ id: 1 });
      User.findByPk.mockResolvedValue(mockUser);
      emailService.auth.sendVerificationEmail.mockRejectedValue(new Error('Email failed'));

      const response = await request(app)
        .post('/auth/resend-verification')
        .set('Cookie', ['accessToken=valid-token']);

      expect(response.status).toBe(500);
      expect(response.body.error).toContain('Failed to send verification email');
    });
  });

  describe('POST /auth/refresh', () => {
    it('should refresh access token with valid refresh token', async () => {
      const mockUser = {
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: true,
        jwtVersion: 1,
        role: 'user'
      };

      jwt.verify.mockReturnValue({ id: 1, type: 'refresh', jwtVersion: 1 });
      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', ['refreshToken=valid-refresh-token']);

      expect(response.status).toBe(200);
      expect(response.body.user).toMatchObject({
        id: 1,
        email: 'test@example.com'
      });
      expect(response.headers['set-cookie']).toEqual(
        expect.arrayContaining([
          expect.stringContaining('accessToken')
        ])
      );
    });

    it('should reject missing refresh token', async () => {
      const response = await request(app)
        .post('/auth/refresh');

      expect(response.status).toBe(401);
      expect(response.body.error).toBe('Refresh token required');
    });

    it('should reject invalid refresh token', async () => {
      jwt.verify.mockImplementation(() => { throw new Error('Invalid token'); });

      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', ['refreshToken=invalid-token']);

      expect(response.status).toBe(401);
      expect(response.body.error).toBe('Invalid or expired refresh token');
    });

    it('should reject non-refresh token type', async () => {
      jwt.verify.mockReturnValue({ id: 1 }); // Missing type: 'refresh'

      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', ['refreshToken=access-token']);

      expect(response.status).toBe(401);
      expect(response.body.error).toBe('Invalid refresh token');
    });

    it('should reject refresh token for non-existent user', async () => {
      jwt.verify.mockReturnValue({ id: 999, type: 'refresh' });
      User.findByPk.mockResolvedValue(null);

      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', ['refreshToken=valid-token']);

      expect(response.status).toBe(401);
      expect(response.body.error).toBe('User not found');
    });

    it('should reject token with mismatched jwtVersion', async () => {
      const mockUser = {
        id: 1,
        jwtVersion: 2 // Different from token's jwtVersion
      };

      jwt.verify.mockReturnValue({ id: 1, type: 'refresh', jwtVersion: 1 });
      User.findByPk.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', ['refreshToken=old-token']);

      expect(response.status).toBe(401);
      expect(response.body.error).toContain('password change');
      expect(response.body.code).toBe('JWT_VERSION_MISMATCH');
    });
  });

  describe('POST /auth/logout', () => {
    it('should logout user and clear cookies', async () => {
      const response = await request(app)
        .post('/auth/logout');

      expect(response.status).toBe(200);
      expect(response.body.message).toBe('Logged out successfully');
      expect(response.headers['set-cookie']).toEqual(
        expect.arrayContaining([
          expect.stringContaining('accessToken=;'),
          expect.stringContaining('refreshToken=;')
        ])
      );
    });
  });

  describe('GET /auth/status', () => {
    it('should return authenticated true when user is logged in', async () => {
      // The optionalAuth middleware sets req.user if authenticated
      // We need to modify the mock for this specific test
      const mockUser = {
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: true
      };

      // Create a custom app for this test with user set
      const statusApp = express();
      statusApp.use(express.json());
      statusApp.use((req, res, next) => {
        req.user = mockUser;
        next();
      });
      statusApp.use('/auth', authRoutes);

      const response = await request(statusApp)
        .get('/auth/status');

      expect(response.status).toBe(200);
      expect(response.body.authenticated).toBe(true);
      expect(response.body.user).toMatchObject({
        id: 1,
        email: 'test@example.com'
      });
    });

    it('should return authenticated false when user is not logged in', async () => {
      const response = await request(app)
        .get('/auth/status');

      expect(response.status).toBe(200);
      expect(response.body.authenticated).toBe(false);
      expect(response.body.user).toBeUndefined();
    });
  });

  describe('POST /auth/forgot-password', () => {
    it('should send password reset email for existing user', async () => {
      const mockUser = {
        id: 1,
        email: 'test@example.com',
        authProvider: 'local',
        generatePasswordResetToken: jest.fn().mockResolvedValue('reset-token')
      };

      User.findOne.mockResolvedValue(mockUser);
      emailService.auth.sendPasswordResetEmail.mockResolvedValue();

      const response = await request(app)
        .post('/auth/forgot-password')
        .send({ email: 'test@example.com' });

      expect(response.status).toBe(200);
      expect(response.body.message).toContain('If an account exists');
      expect(mockUser.generatePasswordResetToken).toHaveBeenCalled();
      expect(emailService.auth.sendPasswordResetEmail).toHaveBeenCalledWith(mockUser, 'reset-token');
    });

    it('should return success even for non-existent email (security)', async () => {
      User.findOne.mockResolvedValue(null);

      const response = await request(app)
        .post('/auth/forgot-password')
        .send({ email: 'nonexistent@example.com' });

      expect(response.status).toBe(200);
      expect(response.body.message).toContain('If an account exists');
    });

    it('should return success for OAuth user (security)', async () => {
      User.findOne.mockResolvedValue(null); // Query for local provider returns null

      const response = await request(app)
        .post('/auth/forgot-password')
        .send({ email: 'google@example.com' });

      expect(response.status).toBe(200);
      expect(response.body.message).toContain('If an account exists');
    });

    it('should handle errors gracefully', async () => {
      User.findOne.mockRejectedValue(new Error('Database error'));

      const response = await request(app)
        .post('/auth/forgot-password')
        .send({ email: 'test@example.com' });

      expect(response.status).toBe(500);
      expect(response.body.error).toContain('Failed to process password reset');
    });
  });

  describe('POST /auth/verify-reset-token', () => {
    it('should verify valid reset token', async () => {
      const mockUser = {
        id: 1,
        passwordResetToken: crypto.createHash('sha256').update('valid-token').digest('hex'),
        isPasswordResetTokenValid: jest.fn().mockReturnValue(true)
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/verify-reset-token')
        .send({ token: 'valid-token' });

      expect(response.status).toBe(200);
      expect(response.body.valid).toBe(true);
    });

    it('should reject invalid reset token', async () => {
      User.findOne.mockResolvedValue(null);

      const response = await request(app)
        .post('/auth/verify-reset-token')
        .send({ token: 'invalid-token' });

      expect(response.status).toBe(400);
      expect(response.body.valid).toBe(false);
      expect(response.body.code).toBe('TOKEN_INVALID');
    });

    it('should reject expired reset token', async () => {
      const mockUser = {
        id: 1,
        isPasswordResetTokenValid: jest.fn().mockReturnValue(false)
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/verify-reset-token')
        .send({ token: 'expired-token' });

      expect(response.status).toBe(400);
      expect(response.body.valid).toBe(false);
      expect(response.body.code).toBe('TOKEN_EXPIRED');
    });
  });

  describe('POST /auth/reset-password', () => {
    it('should reset password with valid token', async () => {
      const mockUser = {
        id: 1,
        email: 'test@example.com',
        passwordResetToken: crypto.createHash('sha256').update('valid-token').digest('hex'),
        isPasswordResetTokenValid: jest.fn().mockReturnValue(true),
        resetPassword: jest.fn().mockResolvedValue()
      };

      User.findOne.mockResolvedValue(mockUser);
      emailService.auth.sendPasswordChangedEmail.mockResolvedValue();

      const response = await request(app)
        .post('/auth/reset-password')
        .send({
          token: 'valid-token',
          newPassword: 'NewStrongPass123!'
        });

      expect(response.status).toBe(200);
      expect(response.body.message).toContain('Password has been reset successfully');
      expect(mockUser.resetPassword).toHaveBeenCalledWith('NewStrongPass123!');
      expect(emailService.auth.sendPasswordChangedEmail).toHaveBeenCalledWith(mockUser);
    });

    it('should reject invalid reset token', async () => {
      User.findOne.mockResolvedValue(null);

      const response = await request(app)
        .post('/auth/reset-password')
        .send({
          token: 'invalid-token',
          newPassword: 'NewStrongPass123!'
        });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('Invalid or expired reset token');
      expect(response.body.code).toBe('TOKEN_INVALID');
    });

    it('should reject expired reset token', async () => {
      const mockUser = {
        id: 1,
        isPasswordResetTokenValid: jest.fn().mockReturnValue(false)
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/reset-password')
        .send({
          token: 'expired-token',
          newPassword: 'NewStrongPass123!'
        });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('expired');
      expect(response.body.code).toBe('TOKEN_EXPIRED');
    });

    it('should handle reset password errors', async () => {
      User.findOne.mockRejectedValue(new Error('Database error'));

      const response = await request(app)
        .post('/auth/reset-password')
        .send({
          token: 'some-token',
          newPassword: 'NewStrongPass123!'
        });

      expect(response.status).toBe(500);
      expect(response.body.error).toContain('Failed to reset password');
    });
  });

  describe('Cookie settings', () => {
    it('should set secure cookies in production', async () => {
      process.env.NODE_ENV = 'prod';

      const mockUser = {
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: true,
        jwtVersion: 1,
        role: 'user',
        isLocked: jest.fn().mockReturnValue(false),
        comparePassword: jest.fn().mockResolvedValue(true),
        resetLoginAttempts: jest.fn().mockResolvedValue()
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'test@example.com',
          password: 'password123'
        });

      expect(response.status).toBe(200);
      expect(response.headers['set-cookie']).toEqual(
        expect.arrayContaining([
          expect.stringMatching(/accessToken=.*Secure/i),
          expect.stringMatching(/refreshToken=.*Secure/i)
        ])
      );
    });

    it('should set httpOnly cookies', async () => {
      const mockUser = {
        id: 1,
        email: 'test@example.com',
        firstName: 'Test',
        lastName: 'User',
        isVerified: true,
        jwtVersion: 1,
        role: 'user',
        isLocked: jest.fn().mockReturnValue(false),
        comparePassword: jest.fn().mockResolvedValue(true),
        resetLoginAttempts: jest.fn().mockResolvedValue()
      };

      User.findOne.mockResolvedValue(mockUser);

      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'test@example.com',
          password: 'password123'
        });

      expect(response.status).toBe(200);
      expect(response.headers['set-cookie']).toEqual(
        expect.arrayContaining([
          expect.stringContaining('HttpOnly'),
          expect.stringContaining('HttpOnly')
        ])
      );
    });
  });
});
