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

// Mock Task model before importing routes
jest.mock('../../models/Task', () => {
  const MockTask = jest.fn().mockImplementation((data) => ({
    _id: data._id || '507f1f77bcf86cd799439013',
    _rev: data._rev || '1-abc',
    type: 'task',
    street: data.street,
    description: data.description,
    status: data.status || 'pending',
    completedBy: data.completedBy,
    completedAt: data.completedAt,
    pointsAwarded: data.pointsAwarded || 10,
    createdAt: data.createdAt || new Date().toISOString(),
    updatedAt: data.updatedAt || new Date().toISOString(),
    save: jest.fn().mockResolvedValue(this),
    populate: jest.fn().mockResolvedValue(this),
    toJSON: jest.fn().mockReturnValue(this),
    toObject: jest.fn().mockReturnValue(this),
    ...data
  }));

  MockTask.findById = jest.fn();
  MockTask.find = jest.fn(() => ({
    sort: jest.fn().mockReturnThis(),
    skip: jest.fn().mockReturnThis(),
    limit: jest.fn().mockResolvedValue([])
  }));
  MockTask.findByUser = jest.fn();
  MockTask.create = jest.fn();
  MockTask.findByIdAndUpdate = jest.fn();
  MockTask.findByIdAndDelete = jest.fn();
  MockTask.countDocuments = jest.fn();

  return MockTask;
});

// Mock User model
jest.mock('../../models/User', () => ({
  findById: jest.fn()
}));

// Mock Street model
jest.mock('../../models/Street', () => ({
  findById: jest.fn()
}));

const taskRoutes = require('../../routes/tasks');
const { createTestUser, createTestStreet, createTestTask } = require('../utils/testHelpers');
const couchdbService = require('../../services/couchdbService');
const Task = require('../../models/Task');
const User = require('../../models/User');
const Street = require('../../models/Street');

const app = express();
app.use(express.json());
app.use('/api/tasks', taskRoutes);

describe('Task Routes', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    
    // Reset default implementations
    Task.findById.mockResolvedValue(null);
    Task.find.mockImplementation(() => ({
      sort: jest.fn().mockReturnThis(),
      skip: jest.fn().mockReturnThis(),
      limit: jest.fn().mockResolvedValue([])
    }));
    Task.findByUser.mockResolvedValue([]);
    Task.countDocuments.mockResolvedValue(0);
    Task.create.mockImplementation((data) => Promise.resolve({
      _id: '507f1f77bcf86cd799439013',
      type: 'task',
      street: data.street,
      description: data.description,
      status: 'pending',
      completedBy: null,
      completedAt: null,
      pointsAwarded: 10,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    }));
    Task.findByIdAndUpdate.mockImplementation((id, data) => Promise.resolve({
      _id: id,
      ...data
    }));
    User.findById.mockResolvedValue({
      _id: '507f1f77bcf86cd799439099',
      name: 'Test User',
      profilePicture: ''
    });
    Street.findById.mockResolvedValue(null);
  });

  describe('GET /api/tasks', () => {
    it('should get all tasks completed by authenticated user', async () => {
      const { user, token } = await createTestUser();
      const userId = user._id;

      const task1 = {
        _id: '507f1f77bcf86cd799439013',
        type: 'task',
        street: {
          streetId: '507f1f77bcf86cd799439011',
          name: 'Test Street',
          location: { lat: 0, lng: 0 }
        },
        description: 'Clean sidewalk',
        completedBy: {
          userId: userId,
          name: user.name,
          profilePicture: ''
        },
        status: 'completed',
        completedAt: new Date().toISOString(),
        pointsAwarded: 10,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        populate: jest.fn().mockResolvedValue(this)
      };

      const task2 = {
        _id: '507f1f77bcf86cd799439014',
        type: 'task',
        street: {
          streetId: '507f1f77bcf86cd799439012',
          name: 'Another Street',
          location: { lat: 1, lng: 1 }
        },
        description: 'Pick up trash',
        completedBy: {
          userId: userId,
          name: user.name,
          profilePicture: ''
        },
        status: 'completed',
        completedAt: new Date().toISOString(),
        pointsAwarded: 10,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        populate: jest.fn().mockResolvedValue(this)
      };

      const mockTasks = [task1, task2];
      Task.find.mockImplementation(() => ({
        sort: jest.fn().mockReturnThis(),
        skip: jest.fn().mockReturnThis(),
        limit: jest.fn().mockResolvedValue(mockTasks)
      }));
      Task.countDocuments.mockResolvedValue(2);

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

      expect(response.body).toHaveProperty('data');
      expect(response.body).toHaveProperty('pagination');
      expect(Array.isArray(response.body.data)).toBe(true);
      expect(response.body.data.length).toBe(2);
    });

    it('should return empty array when user has no completed tasks', async () => {
      const { token } = await createTestUser();

      Task.find.mockImplementation(() => ({
        sort: jest.fn().mockReturnThis(),
        skip: jest.fn().mockReturnThis(),
        limit: jest.fn().mockResolvedValue([])
      }));
      Task.countDocuments.mockResolvedValue(0);

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

      expect(response.body).toHaveProperty('data');
      expect(response.body.data.length).toBe(0);
    });

    it('should not get tasks without authentication', async () => {
      const response = await request(app)
        .get('/api/tasks')
        .expect(401);

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

  describe('POST /api/tasks', () => {
    it('should create a new task with authentication', async () => {
      const { user, token } = await createTestUser();
      const streetId = '507f1f77bcf86cd799439011';

      const taskData = {
        street: streetId,
        description: 'Clean the sidewalk',
      };

      const mockStreet = {
        _id: streetId,
        name: 'Test Street',
        location: { lat: 0, lng: 0 }
      };

      const mockTask = {
        _id: '507f1f77bcf86cd799439013',
        type: 'task',
        street: {
          streetId: streetId,
          name: 'Test Street',
          location: { lat: 0, lng: 0 }
        },
        description: taskData.description,
        status: 'pending',
        pointsAwarded: 10,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      };

      Street.findById.mockResolvedValue(mockStreet);
      Task.create.mockResolvedValue(mockTask);

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

      expect(response.body).toHaveProperty('description', taskData.description);
      expect(response.body).toHaveProperty('street');
      expect(response.body.street).toHaveProperty('streetId', streetId);
    });

    it('should return 404 when street not found', async () => {
      const { user, token } = await createTestUser();

      const taskData = {
        street: '507f1f77bcf86cd799439011',
        description: 'Clean the sidewalk',
      };

      Street.findById.mockResolvedValue(null);

      const response = await request(app)
        .post('/api/tasks')
        .set('x-auth-token', token)
        .send(taskData)
        .expect(404);

      expect(response.body).toHaveProperty('msg', 'Street not found');
    });

    it('should not create task without authentication', async () => {
      const taskData = {
        street: '507f1f77bcf86cd799439011',
        description: 'Clean the sidewalk',
      };

      const response = await request(app)
        .post('/api/tasks')
        .send(taskData)
        .expect(401);

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

  describe('PUT /api/tasks/:id', () => {
    it('should complete a task', async () => {
      const { user, token } = await createTestUser();
      const taskId = '507f1f77bcf86cd799439013';

      const task = {
        _id: taskId,
        type: 'task',
        street: {
          streetId: '507f1f77bcf86cd799439011',
          name: 'Test Street',
          location: { lat: 0, lng: 0 }
        },
        description: 'Clean sidewalk',
        status: 'pending',
        completedBy: null,
        completedAt: null,
        pointsAwarded: 10,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        save: jest.fn().mockResolvedValue(this)
      };

      const mockUser = {
        _id: user._id,
        name: 'Test User',
        profilePicture: ''
      };

      const updatedTask = {
        ...task,
        status: 'completed',
        completedBy: {
          userId: user._id,
          name: 'Test User',
          profilePicture: ''
        },
        completedAt: new Date().toISOString()
      };

      const updatedUser = {
        _id: user._id,
        points: 10
      };

      User.findById.mockResolvedValue(mockUser);
      
      // Mock save method on task instance
      const mockTaskInstance = {
        ...task,
        save: jest.fn().mockResolvedValue(updatedTask)
      };
      Task.findById.mockResolvedValue(mockTaskInstance);

      couchdbService.updateUserPoints.mockResolvedValue(updatedUser);

      const response = await request(app)
        .put(`/api/tasks/${taskId}`)
        .set('x-auth-token', token)
        .expect(200);

      expect(response.body).toHaveProperty('task');
      expect(response.body.task).toHaveProperty('status', 'completed');
      expect(response.body).toHaveProperty('pointsAwarded', 10);
      expect(response.body).toHaveProperty('newBalance', 10);
    });

    it('should return 404 for non-existent task', async () => {
      const { token } = await createTestUser();
      const fakeId = '507f1f77bcf86cd799439011';

      Task.findById.mockResolvedValue(null);

      const response = await request(app)
        .put(`/api/tasks/${fakeId}`)
        .set('x-auth-token', token)
        .expect(404);

      expect(response.body).toHaveProperty('msg', 'Task not found');
    });

    it('should return 400 for already completed task', async () => {
      const { user, token } = await createTestUser();
      const taskId = '507f1f77bcf86cd799439013';

      const task = {
        _id: taskId,
        type: 'task',
        street: {
          streetId: '507f1f77bcf86cd799439011',
          name: 'Test Street',
          location: { lat: 0, lng: 0 }
        },
        description: 'Clean sidewalk',
        status: 'completed',
        completedBy: {
          userId: user._id,
          name: 'Test User',
          profilePicture: ''
        },
        completedAt: new Date().toISOString(),
        pointsAwarded: 10,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      };

      Task.findById.mockResolvedValue(task);

      const response = await request(app)
        .put(`/api/tasks/${taskId}`)
        .set('x-auth-token', token)
        .expect(400);

      expect(response.body).toHaveProperty('msg', 'Task already completed');
    });

    it('should not complete task without authentication', async () => {
      const taskId = '507f1f77bcf86cd799439013';

      const response = await request(app)
        .put(`/api/tasks/${taskId}`)
        .expect(401);

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

    it('should handle invalid task ID format', async () => {
      const { token } = await createTestUser();

      const response = await request(app)
        .put('/api/tasks/invalid-id')
        .set('x-auth-token', token)
        .expect(400);

      expect(response.body).toHaveProperty('success', false);
      expect(response.body.errors).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            field: 'id',
            message: 'Invalid task ID'
          })
        ])
      );
    });
  });
});