/**
 * Authentication Integration Tests
 *
 * These tests use a real database connection to verify the complete
 * authentication flow including user registration, login, token management,
 * and password reset functionality.
 */

const request = require('supertest');
const express = require('express');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');

// Mock rate limiters before importing routes
jest.mock('../../middleware/rateLimiter', () => ({
  registerLimiter: (req, res, next) => next(),
  loginLimiter: (req, res, next) => next(),
  refreshLimiter: (req, res, next) => next(),
  passwordResetLimiter: (req, res, next) => next(),
  passwordResetRequestLimiter: (req, res, next) => next(),
  verifyEmailLimiter: (req, res, next) => next(),
  resendVerificationLimiter: (req, res, next) => next(),
  emailVerificationLimiter: (req, res, next) => next(),
}));

// Mock CSRF protection for tests
jest.mock('../../middleware/csrf', () => ({
  csrfProtection: (req, res, next) => next(),
  getCSRFToken: (req, res) => {
    res.set('x-csrf-token', 'test-csrf-token');
    res.json({ csrfToken: 'test-csrf-token' });
  },
}));

const { sequelize, User, AlphaInvitation } = require('../../models');
const authRoutes = require('../../routes/auth');

// Test app setup
const createTestApp = () => {
  const app = express();
  app.use(express.json());
  app.use(cookieParser());

  // Add request ID middleware
  app.use((req, res, next) => {
    req.id = 'test-request-id';
    next();
  });

  app.use('/auth', authRoutes);
  return app;
};

// Test data factory
const createTestUser = async (overrides = {}) => {
  const defaultData = {
    email: `test-${Date.now()}@example.com`,
    password: 'TestPassword123!',
    firstName: 'Test',
    lastName: 'User',
    isVerified: false,
    authProvider: 'local',
  };

  return User.create({ ...defaultData, ...overrides });
};

const createAlphaInvitation = async (overrides = {}) => {
  // Generate a valid code matching pattern /^ALPHA-[A-Z0-9]{8}$/i
  const randomCode = Math.random().toString(36).substring(2, 10).toUpperCase().padEnd(8, 'X');
  const defaultData = {
    code: `ALPHA-${randomCode.substring(0, 8)}`,
    email: `alpha-${Date.now()}@example.com`, // Email is required
    status: 'pending', // Valid values: pending, active, revoked
  };

  return AlphaInvitation.create({ ...defaultData, ...overrides });
};

describe('Auth Integration Tests', () => {
  let app;

  beforeAll(async () => {
    // Set test environment variables
    process.env.NODE_ENV = 'test';
    process.env.JWT_ACCESS_SECRET = 'test-access-secret';
    process.env.JWT_REFRESH_SECRET = 'test-refresh-secret';
    process.env.ALPHA_TESTING_ENABLED = 'false';

    // Sync database
    await sequelize.sync({ force: true });

    app = createTestApp();
  });

  afterAll(async () => {
    await sequelize.close();
  });

  beforeEach(async () => {
    // Clean up users before each test
    await User.destroy({ where: {}, truncate: true, cascade: true });
    await AlphaInvitation.destroy({ where: {}, truncate: true, cascade: true });
  });

  describe('POST /auth/register', () => {
    it('should register a new user successfully', async () => {
      const userData = {
        email: 'newuser@example.com',
        password: 'SecurePassword123!',
        firstName: 'New',
        lastName: 'User',
      };

      const response = await request(app)
        .post('/auth/register')
        .send(userData)
        .expect(201);

      expect(response.body.user).toBeDefined();
      expect(response.body.user.email).toBe(userData.email);
      expect(response.body.user.firstName).toBe(userData.firstName);
      expect(response.body.user.isVerified).toBe(false);

      // Verify user was created in database
      const user = await User.findOne({ where: { email: userData.email } });
      expect(user).not.toBeNull();
      expect(user.firstName).toBe(userData.firstName);

      // Verify password was hashed
      expect(user.password).not.toBe(userData.password);

      // Verify cookies were set
      expect(response.headers['set-cookie']).toBeDefined();
      const cookies = response.headers['set-cookie'];
      expect(cookies.some(c => c.startsWith('accessToken='))).toBe(true);
      expect(cookies.some(c => c.startsWith('refreshToken='))).toBe(true);
    });

    it('should reject registration with existing email', async () => {
      await createTestUser({ email: 'existing@example.com' });

      const response = await request(app)
        .post('/auth/register')
        .send({
          email: 'existing@example.com',
          password: 'SecurePassword123!',
          firstName: 'Another',
          lastName: 'User',
        })
        .expect(400);

      expect(response.body.error).toBe('Registration failed');
      expect(response.body.details[0].field).toBe('email');
    });

    it('should reject registration with invalid email format', async () => {
      const response = await request(app)
        .post('/auth/register')
        .send({
          email: 'not-an-email',
          password: 'SecurePassword123!',
          firstName: 'Test',
          lastName: 'User',
        })
        .expect(400);

      // Response should contain errors or error message
      expect(response.body.errors || response.body.error).toBeDefined();
    });

    it('should generate verification token on registration', async () => {
      const userData = {
        email: 'verify@example.com',
        password: 'SecurePassword123!',
        firstName: 'Verify',
        lastName: 'User',
      };

      await request(app)
        .post('/auth/register')
        .send(userData)
        .expect(201);

      const user = await User.findOne({ where: { email: userData.email } });
      expect(user.verificationToken).toBeDefined();
      expect(user.verificationTokenExpiry).toBeDefined();
    });
  });

  describe('POST /auth/login', () => {
    let testUser;

    beforeEach(async () => {
      testUser = await createTestUser({
        email: 'login@example.com',
        password: 'TestPassword123!',
        isVerified: true,
      });
    });

    it('should login with valid credentials', async () => {
      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'login@example.com',
          password: 'TestPassword123!',
        })
        .expect(200);

      expect(response.body.user).toBeDefined();
      expect(response.body.user.email).toBe('login@example.com');

      // Verify cookies were set
      const cookies = response.headers['set-cookie'];
      expect(cookies.some(c => c.startsWith('accessToken='))).toBe(true);
      expect(cookies.some(c => c.startsWith('refreshToken='))).toBe(true);
    });

    it('should reject login with wrong password', async () => {
      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'login@example.com',
          password: 'WrongPassword!',
        })
        .expect(401);

      expect(response.body.error).toBe('Unable to log in. Please check your email and password, or create an account.');
    });

    it('should reject login with non-existent email', async () => {
      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'nonexistent@example.com',
          password: 'SomePassword123!',
        })
        .expect(401);

      expect(response.body.error).toBe('Unable to log in. Please check your email and password, or create an account.');
    });

    it('should increment login attempts on failed login', async () => {
      await request(app)
        .post('/auth/login')
        .send({
          email: 'login@example.com',
          password: 'WrongPassword!',
        })
        .expect(401);

      const user = await User.findOne({ where: { email: 'login@example.com' } });
      expect(user.loginAttempts).toBe(1);
    });

    it('should lock account after too many failed attempts', async () => {
      // Make 5 failed login attempts
      for (let i = 0; i < 5; i++) {
        await request(app)
          .post('/auth/login')
          .send({
            email: 'login@example.com',
            password: 'WrongPassword!',
          });
      }

      // 6th attempt should return locked error
      const response = await request(app)
        .post('/auth/login')
        .send({
          email: 'login@example.com',
          password: 'TestPassword123!', // Correct password
        })
        .expect(423);

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

    it('should reset login attempts on successful login', async () => {
      // First fail a login
      await request(app)
        .post('/auth/login')
        .send({
          email: 'login@example.com',
          password: 'WrongPassword!',
        });

      // Verify attempts incremented
      let user = await User.findOne({ where: { email: 'login@example.com' } });
      expect(user.loginAttempts).toBe(1);

      // Now login successfully
      await request(app)
        .post('/auth/login')
        .send({
          email: 'login@example.com',
          password: 'TestPassword123!',
        })
        .expect(200);

      // Verify attempts reset
      user = await User.findOne({ where: { email: 'login@example.com' } });
      expect(user.loginAttempts).toBe(0);
    });
  });

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

      expect(response.body.message).toBe('Logged out successfully');

      // Verify cookies are cleared
      const cookies = response.headers['set-cookie'];
      expect(cookies.some(c => c.includes('accessToken=;'))).toBe(true);
      expect(cookies.some(c => c.includes('refreshToken=;'))).toBe(true);
    });
  });

  describe('POST /auth/refresh', () => {
    let testUser;

    beforeEach(async () => {
      testUser = await createTestUser({
        email: 'refresh@example.com',
        isVerified: true,
      });
    });

    it('should refresh access token with valid refresh token', async () => {
      // Create a valid refresh token
      const refreshToken = jwt.sign(
        { id: testUser.id, jwtVersion: testUser.jwtVersion, type: 'refresh' },
        process.env.JWT_REFRESH_SECRET,
        { expiresIn: '7d' }
      );

      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', [`refreshToken=${refreshToken}`])
        .expect(200);

      expect(response.body.user).toBeDefined();
      expect(response.body.user.email).toBe('refresh@example.com');

      // Verify new access token cookie was set
      const cookies = response.headers['set-cookie'];
      expect(cookies.some(c => c.startsWith('accessToken='))).toBe(true);
    });

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

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

    it('should reject refresh with invalid token', async () => {
      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', ['refreshToken=invalid-token'])
        .expect(401);

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

    it('should reject refresh with outdated JWT version', async () => {
      // Create refresh token with old JWT version
      const refreshToken = jwt.sign(
        { id: testUser.id, jwtVersion: testUser.jwtVersion - 1, type: 'refresh' },
        process.env.JWT_REFRESH_SECRET,
        { expiresIn: '7d' }
      );

      const response = await request(app)
        .post('/auth/refresh')
        .set('Cookie', [`refreshToken=${refreshToken}`])
        .expect(401);

      expect(response.body.code).toBe('JWT_VERSION_MISMATCH');
    });
  });

  describe('GET /auth/status', () => {
    let testUser;

    beforeEach(async () => {
      testUser = await createTestUser({
        email: 'status@example.com',
        isVerified: true,
      });
    });

    it('should return authenticated status with valid token', async () => {
      const accessToken = jwt.sign(
        { id: testUser.id, jwtVersion: testUser.jwtVersion },
        process.env.JWT_ACCESS_SECRET,
        { expiresIn: '15m' }
      );

      const response = await request(app)
        .get('/auth/status')
        .set('Cookie', [`accessToken=${accessToken}`])
        .expect(200);

      expect(response.body.authenticated).toBe(true);
      expect(response.body.user.email).toBe('status@example.com');
    });

    it('should return unauthenticated status without token', async () => {
      const response = await request(app)
        .get('/auth/status')
        .expect(200);

      expect(response.body.authenticated).toBe(false);
    });
  });

  describe('POST /auth/verify-email', () => {
    let testUser;
    let verificationCode;
    let accessToken;

    beforeEach(async () => {
      testUser = await createTestUser({
        email: 'unverified@example.com',
        isVerified: false,
      });
      await testUser.generateVerificationToken();
      await testUser.reload();
      verificationCode = testUser.verificationToken; // Now a 6-digit code

      // Generate access token for authentication
      accessToken = jwt.sign(
        { id: testUser.id, email: testUser.email, jwtVersion: testUser.jwtVersion || 0 },
        process.env.JWT_ACCESS_SECRET || 'test-access-secret',
        { expiresIn: '15m' }
      );
    });

    it('should verify email with valid code', async () => {
      const response = await request(app)
        .post('/auth/verify-email')
        .set('Cookie', `accessToken=${accessToken}`)
        .send({ code: verificationCode })
        .expect(200);

      expect(response.body.message).toBe('Email verified successfully');
      expect(response.body.user.isVerified).toBe(true);

      // Verify in database
      await testUser.reload();
      expect(testUser.isVerified).toBe(true);
      expect(testUser.verificationToken).toBeNull();
    });

    it('should reject verification with invalid code', async () => {
      const response = await request(app)
        .post('/auth/verify-email')
        .set('Cookie', `accessToken=${accessToken}`)
        .send({ code: '000000' })
        .expect(400);

      expect(response.body.code).toBe('VERIFICATION_INVALID');
    });

    it('should reject verification for already verified user', async () => {
      // First verify the user
      await testUser.verifyEmail();

      const response = await request(app)
        .post('/auth/verify-email')
        .set('Cookie', `accessToken=${accessToken}`)
        .send({ code: verificationCode })
        .expect(400);

      expect(response.body.code).toBe('ALREADY_VERIFIED');
    });
  });

  describe('Password Reset Flow', () => {
    let testUser;

    beforeEach(async () => {
      testUser = await createTestUser({
        email: 'reset@example.com',
        isVerified: true,
        authProvider: 'local',
      });
    });

    describe('POST /auth/forgot-password', () => {
      it('should accept valid email and generate reset token', async () => {
        const response = await request(app)
          .post('/auth/forgot-password')
          .send({ email: 'reset@example.com' })
          .expect(200);

        expect(response.body.message).toContain('If an account exists');

        // Verify token was generated in database
        await testUser.reload();
        expect(testUser.passwordResetToken).toBeDefined();
        expect(testUser.passwordResetTokenExpiry).toBeDefined();
      });

      it('should return success even for non-existent email (security)', async () => {
        const response = await request(app)
          .post('/auth/forgot-password')
          .send({ email: 'nonexistent@example.com' })
          .expect(200);

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

    describe('POST /auth/reset-password', () => {
      let resetToken;

      beforeEach(async () => {
        resetToken = await testUser.generatePasswordResetToken();
      });

      it('should reset password with valid token', async () => {
        const newPassword = 'NewSecurePassword123!';

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

        expect(response.body.message).toContain('Password has been reset');

        // Verify password was changed
        await testUser.reload();
        const isValid = await testUser.comparePassword(newPassword);
        expect(isValid).toBe(true);

        // Verify token was cleared
        expect(testUser.passwordResetToken).toBeNull();
      });

      it('should reject reset with invalid token', async () => {
        const response = await request(app)
          .post('/auth/reset-password')
          .send({ token: 'invalid-token', newPassword: 'NewPassword123!' })
          .expect(400);

        // Response should contain error (format may vary based on validation)
        expect(response.body.error || response.body.errors).toBeDefined();
      });

      it('should increment JWT version after password reset', async () => {
        const oldJwtVersion = testUser.jwtVersion;

        await request(app)
          .post('/auth/reset-password')
          .send({ token: resetToken, newPassword: 'NewPassword123!' })
          .expect(200);

        await testUser.reload();
        expect(testUser.jwtVersion).toBe(oldJwtVersion + 1);
      });
    });
  });

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

      expect(response.headers['x-csrf-token']).toBeDefined();
    });
  });

  describe('Alpha Testing Mode', () => {
    beforeEach(() => {
      process.env.ALPHA_TESTING_ENABLED = 'true';
    });

    afterEach(() => {
      process.env.ALPHA_TESTING_ENABLED = 'false';
    });

    it('should reject registration without alpha code when enabled', async () => {
      const response = await request(app)
        .post('/auth/register')
        .send({
          email: 'alpha@example.com',
          password: 'SecurePassword123!',
          firstName: 'Alpha',
          lastName: 'User',
        })
        .expect(403);

      expect(response.body.error).toContain('Alpha access required');
    });

    it('should allow registration with valid alpha code', async () => {
      const validCode = 'ALPHA-TEST1234';
      const invitation = await createAlphaInvitation({
        code: validCode,
        email: 'invited@example.com', // Required field
      });

      // Cookie-parser parses JSON cookies that start with 'j:'
      const cookieValue = `j:${JSON.stringify({ code: validCode })}`;

      const response = await request(app)
        .post('/auth/register')
        .set('Cookie', [`alphaAccessCode=${cookieValue}`])
        .send({
          email: 'alphauser@example.com',
          password: 'SecurePassword123!',
          firstName: 'Alpha',
          lastName: 'User',
        })
        .expect(201);

      expect(response.body.user.email).toBe('alphauser@example.com');

      // Verify invitation was linked
      await invitation.reload();
      expect(invitation.usedBy).toBeDefined();
      expect(invitation.status).toBe('active');
    });
  });
});
