import {
  createTask,
  listTasks,
  getTask,
  updateTask,
  deleteTask,
  reorderTasks
} from '../../services/taskService.js';

jest.mock('../../db/connection.js', () => ({
  pool: {
    query: jest.fn(),
    connect: jest.fn()
  }
}));

import { pool } from '../../db/connection.js';

describe('Task Service', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });
  
  describe('createTask', () => {
    it('should create a task at the end of project', async () => {
      const mockClient = {
        query: jest.fn(),
        release: jest.fn()
      };
      
      pool.connect.mockResolvedValue(mockClient);
      
      // Project exists
      mockClient.query
        .mockResolvedValueOnce({ rows: [{ id: 1 }] })
        // Max position query
        .mockResolvedValueOnce({ rows: [{ next_position: 3 }] })
        // Insert task
        .mockResolvedValueOnce({ rows: [{
          id: 1,
          project_id: 1,
          title: 'New Task',
          status: 'backlog',
          position: 3
        }] });
      
      const result = await createTask(1, {
        title: 'New Task',
        status: 'backlog'
      }, 1);
      
      expect(result.position).toBe(3);
      expect(result.title).toBe('New Task');
      expect(mockClient.query).toHaveBeenCalledWith('BEGIN');
      expect(mockClient.query).toHaveBeenCalledWith('COMMIT');
    });
    
    it('should rollback on error', async () => {
      const mockClient = {
        query: jest.fn(),
        release: jest.fn()
      };
      
      pool.connect.mockResolvedValue(mockClient);
      mockClient.query.mockRejectedValueOnce(new Error('DB Error'));
      
      await expect(createTask(1, { title: 'Task' }, 1)).rejects.toThrow();
      expect(mockClient.query).toHaveBeenCalledWith('ROLLBACK');
    });
  });
  
  describe('listTasks', () => {
    it('should list all tasks in a project', async () => {
      const mockTasks = [
        { id: 1, title: 'Task 1', status: 'backlog', position: 0 },
        { id: 2, title: 'Task 2', status: 'in_progress', position: 1 }
      ];
      
      pool.query
        .mockResolvedValueOnce({ rows: [{ id: 1 }] }) // Project check
        .mockResolvedValueOnce({ rows: mockTasks })   // Tasks query
        .mockResolvedValueOnce({ rows: [{ total: 2 }] }); // Count query
      
      const result = await listTasks(1, {
        status: 'all',
        sort: 'position',
        limit: 50,
        offset: 0
      });
      
      expect(result.tasks).toEqual(mockTasks);
      expect(result.total).toBe(2);
    });
    
    it('should filter tasks by status', async () => {
      pool.query
        .mockResolvedValueOnce({ rows: [{ id: 1 }] })
        .mockResolvedValueOnce({ rows: [] })
        .mockResolvedValueOnce({ rows: [{ total: 0 }] });
      
      await listTasks(1, {
        status: 'done',
        sort: 'position',
        limit: 50,
        offset: 0
      });
      
      const query = pool.query.mock.calls[1][0];
      expect(query).toContain("status = 'done'");
    });
  });
  
  describe('getTask', () => {
    it('should get a task by ID', async () => {
      const mockTask = {
        id: 1,
        project_id: 1,
        title: 'Test Task',
        status: 'backlog'
      };
      
      pool.query.mockResolvedValue({ rows: [mockTask] });
      
      const result = await getTask(1, 1);
      
      expect(result).toEqual(mockTask);
    });
    
    it('should throw 404 if task not found', async () => {
      pool.query.mockResolvedValue({ rows: [] });
      
      await expect(getTask(1, 999)).rejects.toThrow();
    });
  });
  
  describe('updateTask', () => {
    it('should update task without reordering', async () => {
      const mockClient = {
        query: jest.fn(),
        release: jest.fn()
      };
      
      pool.connect.mockResolvedValue(mockClient);
      
      mockClient.query
        .mockResolvedValueOnce({ rows: [{ id: 1, position: 2 }] })
        .mockResolvedValueOnce({ rows: [{
          id: 1,
          title: 'Updated Task',
          status: 'in_progress'
        }] });
      
      const result = await updateTask(1, 1, {
        title: 'Updated Task',
        status: 'in_progress'
      });
      
      expect(result.title).toBe('Updated Task');
    });
    
    it('should reorder task when position is provided', async () => {
      const mockClient = {
        query: jest.fn(),
        release: jest.fn()
      };
      
      pool.connect.mockResolvedValue(mockClient);
      
      const allTaskIds = [1, 2, 3, 4, 5];
      mockClient.query
        .mockResolvedValueOnce({ rows: [{ id: 1, position: 4 }] }) // Get task
        .mockResolvedValueOnce({ rows: [{ total: 5 }] }) // Count
        .mockResolvedValueOnce({ rows: allTaskIds.map(id => ({ id })) }) // All tasks
        .mockResolvedValueOnce({ rowCount: 1 }) // Update first
        .mockResolvedValueOnce({ rowCount: 1 }) // Update second
        .mockResolvedValueOnce({ rowCount: 1 }) // Update third
        .mockResolvedValueOnce({ rowCount: 1 }) // Update fourth
        .mockResolvedValueOnce({ rowCount: 1 }) // Update fifth
        .mockResolvedValueOnce({ rows: [{ id: 1, position: 1 }] }); // Final update
      
      const result = await updateTask(1, 1, {
        position: 1
      });
      
      expect(result.position).toBe(1);
    });
  });
  
  describe('deleteTask', () => {
    it('should delete a task', async () => {
      pool.query.mockResolvedValue({ rows: [{ id: 1 }] });
      
      const result = await deleteTask(1, 1);
      
      expect(result.id).toBe(1);
    });
    
    it('should throw 404 if task not found', async () => {
      pool.query.mockResolvedValue({ rows: [] });
      
      await expect(deleteTask(1, 999)).rejects.toThrow();
    });
  });
  
  describe('reorderTasks', () => {
    it('should reorder multiple tasks', async () => {
      const mockClient = {
        query: jest.fn(),
        release: jest.fn()
      };
      
      pool.connect.mockResolvedValue(mockClient);
      
      const order = [3, 1, 2];
      mockClient.query
        .mockResolvedValueOnce({ rows: [{ count: 3 }] }) // Validation
        .mockResolvedValueOnce({ rowCount: 1 }) // Update task 1
        .mockResolvedValueOnce({ rowCount: 1 }) // Update task 2
        .mockResolvedValueOnce({ rowCount: 1 }); // Update task 3
      
      const result = await reorderTasks(1, order);
      
      expect(result).toBe(3);
    });
  });
});
