const request = require('supertest');
const express = require('express');

// Mock CouchDB service before importing routes
jest.mock('../../services/couchdbService', () => ({
  initialize: jest.fn().mockResolvedValue(true),
  create: jest.fn(),
  getById: jest.fn(),
  find: jest.fn(),
  createDocument: jest.fn(),
  updateDocument: jest.fn(),
  deleteDocument: jest.fn(),
  findByType: jest.fn().mockResolvedValue([]),
  findUserById: jest.fn(),
  findUserByEmail: jest.fn(),
  update: jest.fn(),
  getDocument: jest.fn(),
}));

const authRoutes = require('../../routes/auth');
const User = require('../../models/User');
const couchdbService = require('../../services/couchdbService');
const { createTestUser } = require('../utils/testHelpers');

// Mock User.findOne method for login tests
jest.spyOn(User, 'findOne');

// Create Express app for testing
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);

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

  describe('POST /api/auth/register', () => {
    it('should register a new user and return a token', async () => {
      const userData = {
        name: 'John Doe',
        email: 'john@example.com',
        password: 'Password123',
      };

      // Mock CouchDB responses
      couchdbService.findUserByEmail.mockResolvedValue(null);
      
      const mockCreatedUser = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      couchdbService.createDocument.mockResolvedValue(mockCreatedUser);

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

      expect(response.body).toHaveProperty('token');
      expect(typeof response.body.token).toBe('string');
      expect(response.body.success).toBe(true);

      // Verify CouchDB service was called correctly
      expect(couchdbService.findUserByEmail).toHaveBeenCalledWith(userData.email);
      expect(couchdbService.createDocument).toHaveBeenCalled();
    });

    it('should not register a user with an existing email', async () => {
      const existingUserData = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        email: 'existing@example.com',
        name: 'Existing User',
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };
      
      const existingUser = new User(existingUserData);
      couchdbService.findUserByEmail.mockResolvedValue(existingUser.toJSON());

      const userData = {
        name: 'Jane Doe',
        email: 'existing@example.com',
        password: 'Password123',
      };

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

      expect(response.body).toHaveProperty('msg', 'User already exists');
      expect(response.body.success).toBe(false);
    });

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

      expect(response.body).toBeDefined();
      expect(response.body.success).toBe(false);
      expect(response.body.errors).toBeDefined();
    });
  });

  describe('POST /api/auth/login', () => {
    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should login with valid credentials and return a token', async () => {
      const mockUserData = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        name: 'Test User',
        email: 'login@example.com',
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };
      
      const mockUser = new User(mockUserData);
      jest.spyOn(mockUser, 'comparePassword').mockResolvedValue(true);
      User.findOne.mockResolvedValue(mockUser);

      const loginData = {
        email: 'login@example.com',
        password: 'Password123',
      };

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

      expect(response.body).toHaveProperty('token');
      expect(typeof response.body.token).toBe('string');
      expect(response.body.success).toBe(true);
    });

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

      const loginData = {
        email: 'nonexistent@example.com',
        password: 'password123',
      };

      const response = await request(app)
        .post('/api/auth/login')
        .send(loginData)
        .expect(400);

      expect(response.body).toHaveProperty('msg', 'Invalid credentials');
      expect(response.body.success).toBe(false);
    });

    it('should not login with invalid password', async () => {
      const mockUserData = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        name: 'Test User',
        email: 'login@example.com',
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };
      
      const mockUser = new User(mockUserData);
      jest.spyOn(mockUser, 'comparePassword').mockResolvedValue(false);
      User.findOne.mockResolvedValue(mockUser);

      const loginData = {
        email: 'login@example.com',
        password: 'WrongPassword123',
      };

      const response = await request(app)
        .post('/api/auth/login')
        .send(loginData)
        .expect(400);

      expect(response.body).toHaveProperty('msg', 'Invalid credentials');
      expect(response.body.success).toBe(false);
    });

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

      expect(response.body).toBeDefined();
      expect(response.body.success).toBe(false);
      expect(response.body.errors).toBeDefined();
    });
  });

  describe('GET /api/auth', () => {
    it('should get authenticated user with valid token', async () => {
      const mockUserData = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        name: 'Test User',
        email: 'test@example.com',
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };
      
      const mockUser = new User(mockUserData);
      couchdbService.findUserById.mockResolvedValue(mockUser.toJSON());

      // Create a valid token
      const jwt = require('jsonwebtoken');
      const token = jwt.sign(
        { user: { id: 'user_123' } },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      const response = await request(app)
        .get('/api/auth')
        .set('x-auth-token', token)
        .expect(200);

      expect(response.body).toHaveProperty('_id', 'user_123');
      expect(response.body).toHaveProperty('name', 'Test User');
      expect(response.body).toHaveProperty('email', 'test@example.com');
      expect(response.body).not.toHaveProperty('password');
    });

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

      expect(response.body).toHaveProperty('msg', 'No token, authorization denied');
    });

    it('should reject request with invalid token', async () => {
      const response = await request(app)
        .get('/api/auth')
        .set('x-auth-token', 'invalid-token')
        .expect(401);

      expect(response.body).toHaveProperty('msg', 'Token is not valid');
    });
  });
});
