/**
 * Integration Tests for Authentication Routes
 *
 * Tests user registration, login, token refresh, email verification, and password reset
 */

// Mock queue BEFORE any imports to prevent Redis connection
jest.mock('../../../queue', () => ({
  translationQueue: {
    add: jest.fn().mockResolvedValue({ id: 'mock-job-id' }),
    process: jest.fn(),
    on: jest.fn(),
    close: jest.fn().mockResolvedValue(undefined),
    getJob: jest.fn().mockResolvedValue(null),
    getJobs: jest.fn().mockResolvedValue([]),
    pause: jest.fn().mockResolvedValue(undefined),
    resume: jest.fn().mockResolvedValue(undefined),
    clean: jest.fn().mockResolvedValue([]),
    empty: jest.fn().mockResolvedValue(undefined),
  },
}));

import request from 'supertest';
import { createServer } from '../../../server';
import { prismaMock } from '../../setup';
import { generateRefreshToken } from '../../../auth/jwtService';
import { hashPassword } from '../../../utils/encryption';
import { Application } from 'express';

describe('Auth Routes', () => {
  let app: Application;

  beforeAll(() => {
    app = createServer();
  });

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

      // Mock: User doesn't exist
      prismaMock.user.findUnique.mockResolvedValue(null);

      // Mock: User creation
      prismaMock.user.create.mockResolvedValue({
        id: 'user_123',
        email: userData.email,
        passwordHash: 'hashed_password',
        emailVerificationToken: 'verification_token',
        status: 'active',
        emailVerified: false,
        createdAt: new Date(),
        updatedAt: new Date(),
      } as any);

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

      expect(response.body).toMatchObject({
        message: 'User registered successfully. Please check your email to verify your account.',
        userId: 'user_123',
      });
    });

    it('should reject registration with existing email', async () => {
      const userData = {
        email: 'existing@example.com',
        password: 'SecurePassword123!',
      };

      // Mock: User already exists
      prismaMock.user.findUnique.mockResolvedValue({
        id: 'user_456',
        email: userData.email,
      } as any);

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

      expect(response.body.error.code).toBe('USER_EXISTS');
      expect(response.body.error.message).toContain('already exists');
    });

    it('should validate email format', async () => {
      const response = await request(app)
        .post('/v1/auth/register')
        .send({
          email: 'invalid-email',
          password: 'SecurePassword123!',
        })
        .expect(400);

      expect(response.body.error).toBeDefined();
    });

    it('should validate password length', async () => {
      const response = await request(app)
        .post('/v1/auth/register')
        .send({
          email: 'test@example.com',
          password: 'short',
        })
        .expect(400);

      expect(response.body.error).toBeDefined();
    });

    it('should reject missing email', async () => {
      const response = await request(app)
        .post('/v1/auth/register')
        .send({
          password: 'SecurePassword123!',
        })
        .expect(400);

      expect(response.body.error).toBeDefined();
    });

    it('should reject missing password', async () => {
      const response = await request(app)
        .post('/v1/auth/register')
        .send({
          email: 'test@example.com',
        })
        .expect(400);

      expect(response.body.error).toBeDefined();
    });
  });

  describe('POST /v1/auth/login', () => {
    it('should login successfully with valid credentials', async () => {
      const credentials = {
        email: 'user@example.com',
        password: 'SecurePassword123!',
      };

      const passwordHash = await hashPassword(credentials.password);

      // Mock: User exists
      prismaMock.user.findUnique.mockResolvedValue({
        id: 'user_123',
        email: credentials.email,
        password_hash: passwordHash,
        status: 'active',
        email_verified: true,
        subscriptions: [
          {
            plugin: 'translate',
            plan_tier: 'professional',
            status: 'active',
          },
        ],
      } as any);

      const response = await request(app)
        .post('/v1/auth/login')
        .send(credentials)
        .expect(200);

      expect(response.body).toHaveProperty('accessToken');
      expect(response.body).toHaveProperty('refreshToken');
      expect(response.body.user).toMatchObject({
        id: 'user_123',
        email: credentials.email,
        plan: 'professional',
        subscriptionStatus: 'active',
      });
    });

    it('should reject login with invalid email', async () => {
      const credentials = {
        email: 'nonexistent@example.com',
        password: 'SecurePassword123!',
      };

      // Mock: User doesn't exist
      prismaMock.user.findUnique.mockResolvedValue(null);

      const response = await request(app)
        .post('/v1/auth/login')
        .send(credentials)
        .expect(401);

      expect(response.body.error.code).toBe('INVALID_CREDENTIALS');
    });

    it('should reject login with invalid password', async () => {
      const credentials = {
        email: 'user@example.com',
        password: 'WrongPassword123!',
      };

      const correctPasswordHash = await hashPassword('CorrectPassword123!');

      // Mock: User exists
      prismaMock.user.findUnique.mockResolvedValue({
        id: 'user_123',
        email: credentials.email,
        password_hash: correctPasswordHash,
        status: 'active',
        subscriptions: [],
      } as any);

      const response = await request(app)
        .post('/v1/auth/login')
        .send(credentials)
        .expect(401);

      expect(response.body.error.code).toBe('INVALID_CREDENTIALS');
    });

    it('should reject login for suspended account', async () => {
      const credentials = {
        email: 'suspended@example.com',
        password: 'SecurePassword123!',
      };

      const passwordHash = await hashPassword(credentials.password);

      // Mock: Suspended user
      prismaMock.user.findUnique.mockResolvedValue({
        id: 'user_456',
        email: credentials.email,
        password_hash: passwordHash,
        status: 'suspended',
        subscriptions: [],
      } as any);

      const response = await request(app)
        .post('/v1/auth/login')
        .send(credentials)
        .expect(403);

      expect(response.body.error.code).toBe('ACCOUNT_SUSPENDED');
    });

    it('should handle user without subscription', async () => {
      const credentials = {
        email: 'user@example.com',
        password: 'SecurePassword123!',
      };

      const passwordHash = await hashPassword(credentials.password);

      // Mock: User without subscription
      prismaMock.user.findUnique.mockResolvedValue({
        id: 'user_123',
        email: credentials.email,
        password_hash: passwordHash,
        status: 'active',
        subscriptions: [],
      } as any);

      const response = await request(app)
        .post('/v1/auth/login')
        .send(credentials)
        .expect(200);

      expect(response.body.user.plan).toBe('starter'); // Default plan
      expect(response.body.user.subscriptionStatus).toBe('active'); // Default status
    });
  });

  describe('POST /v1/auth/refresh', () => {
    it('should refresh access token with valid refresh token', async () => {
      const userId = 'user_123';
      const refreshToken = generateRefreshToken(userId);

      // Mock: User exists
      prismaMock.user.findUnique.mockResolvedValue({
        id: userId,
        email: 'user@example.com',
        status: 'active',
        subscriptions: [
          {
            plugin: 'translate',
            plan_tier: 'professional',
            status: 'active',
          },
        ],
      } as any);

      const response = await request(app)
        .post('/v1/auth/refresh')
        .send({ refreshToken })
        .expect(200);

      expect(response.body).toHaveProperty('accessToken');
      expect(response.body.accessToken).toBeTruthy();
    });

    it('should reject expired refresh token', async () => {
      const response = await request(app)
        .post('/v1/auth/refresh')
        .send({ refreshToken: 'invalid.expired.token' })
        .expect(500);

      // Will fail JWT verification
      expect(response.body.error).toBeDefined();
    });

    it('should reject refresh for suspended account', async () => {
      const userId = 'user_456';
      const refreshToken = generateRefreshToken(userId);

      // Mock: Suspended user
      prismaMock.user.findUnique.mockResolvedValue({
        id: userId,
        email: 'suspended@example.com',
        status: 'suspended',
      } as any);

      const response = await request(app)
        .post('/v1/auth/refresh')
        .send({ refreshToken })
        .expect(401);

      expect(response.body.error.code).toBe('INVALID_TOKEN');
    });

    it('should reject refresh for non-existent user', async () => {
      const userId = 'nonexistent_user';
      const refreshToken = generateRefreshToken(userId);

      // Mock: User doesn't exist
      prismaMock.user.findUnique.mockResolvedValue(null);

      const response = await request(app)
        .post('/v1/auth/refresh')
        .send({ refreshToken })
        .expect(401);

      expect(response.body.error.code).toBe('INVALID_TOKEN');
    });
  });

  describe('POST /v1/auth/verify-email', () => {
    it('should verify email with valid token', async () => {
      const verificationToken = 'valid_verification_token';

      // Mock: Find user with token
      prismaMock.user.findFirst.mockResolvedValue({
        id: 'user_123',
        email: 'user@example.com',
        emailVerificationToken: verificationToken,
        emailVerified: false,
      } as any);

      // Mock: Update user
      prismaMock.user.update.mockResolvedValue({
        id: 'user_123',
        emailVerified: true,
        emailVerificationToken: null,
      } as any);

      const response = await request(app)
        .post('/v1/auth/verify-email')
        .send({ token: verificationToken })
        .expect(200);

      expect(response.body.message).toContain('verified successfully');
    });

    it('should reject invalid verification token', async () => {
      // Mock: No user found with token
      prismaMock.user.findFirst.mockResolvedValue(null);

      const response = await request(app)
        .post('/v1/auth/verify-email')
        .send({ token: 'invalid_token' })
        .expect(400);

      expect(response.body.error.code).toBe('INVALID_TOKEN');
    });
  });

  describe('POST /v1/auth/forgot-password', () => {
    it('should send password reset for existing user', async () => {
      const email = 'user@example.com';

      // Mock: User exists
      prismaMock.user.findUnique.mockResolvedValue({
        id: 'user_123',
        email,
      } as any);

      // Mock: Update with reset token
      prismaMock.user.update.mockResolvedValue({
        id: 'user_123',
        passwordResetToken: 'reset_token',
        passwordResetExpires: new Date(),
      } as any);

      const response = await request(app)
        .post('/v1/auth/forgot-password')
        .send({ email })
        .expect(200);

      expect(response.body.message).toContain('password reset link');
    });

    it('should return same message for non-existent user (security)', async () => {
      // Mock: User doesn't exist
      prismaMock.user.findUnique.mockResolvedValue(null);

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

      // Should not reveal if user exists
      expect(response.body.message).toContain('password reset link');
    });

    it('should validate email format', async () => {
      const response = await request(app)
        .post('/v1/auth/forgot-password')
        .send({ email: 'invalid-email' })
        .expect(400);

      expect(response.body.error).toBeDefined();
    });
  });

  describe('POST /v1/auth/reset-password', () => {
    it('should reset password with valid token', async () => {
      const resetData = {
        token: 'valid_reset_token',
        password: 'NewSecurePassword123!',
      };

      // Mock: Find user with valid reset token
      prismaMock.user.findFirst.mockResolvedValue({
        id: 'user_123',
        email: 'user@example.com',
        passwordResetToken: resetData.token,
        passwordResetExpires: new Date(Date.now() + 3600000), // 1 hour from now
      } as any);

      // Mock: Update password
      prismaMock.user.update.mockResolvedValue({
        id: 'user_123',
        passwordResetToken: null,
        passwordResetExpires: null,
      } as any);

      const response = await request(app)
        .post('/v1/auth/reset-password')
        .send(resetData)
        .expect(200);

      expect(response.body.message).toContain('reset successfully');
    });

    it('should reject expired reset token', async () => {
      const resetData = {
        token: 'expired_token',
        password: 'NewSecurePassword123!',
      };

      // Mock: Token expired
      prismaMock.user.findFirst.mockResolvedValue(null);

      const response = await request(app)
        .post('/v1/auth/reset-password')
        .send(resetData)
        .expect(400);

      expect(response.body.error.code).toBe('INVALID_TOKEN');
    });

    it('should validate new password length', async () => {
      const response = await request(app)
        .post('/v1/auth/reset-password')
        .send({
          token: 'valid_token',
          password: 'short',
        })
        .expect(400);

      expect(response.body.error).toBeDefined();
    });
  });
});
