/**
 * Tests unitaires pour TasksService
 */

/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TasksService } from '../tasks';
import { prisma } from '@/services/core/database';
import { tagsService } from '../tags';
import { getReadonlyFieldsForTask } from '../readonly-fields';
import { getToday } from '@/lib/date-utils';

// Mock de prisma
vi.mock('@/services/core/database', () => ({
  prisma: {
    task: {
      findMany: vi.fn(),
      findFirst: vi.fn(),
      findUnique: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      aggregate: vi.fn(),
    },
    taskTag: {
      createMany: vi.fn(),
      deleteMany: vi.fn(),
      findMany: vi.fn(),
    },
    dailyCheckbox: {
      findMany: vi.fn(),
    },
  },
}));

// Mock de tagsService
vi.mock('../tags', () => ({
  tagsService: {
    ensureTagsExist: vi.fn(),
  },
}));

// Mock de readonly-fields
vi.mock('../readonly-fields', () => ({
  getReadonlyFieldsForTask: vi.fn(),
}));

// Mock de date-utils
vi.mock('@/lib/date-utils', () => ({
  getToday: vi.fn(),
}));

describe('TasksService', () => {
  let service: TasksService;
  const mockUserId = 'user-123';

  beforeEach(() => {
    vi.clearAllMocks();
    service = new TasksService();
    vi.mocked(getToday).mockReturnValue(new Date('2024-01-15'));
  });

  describe('getTasks', () => {
    it('devrait récupérer toutes les tâches pour un utilisateur', async () => {
      const mockTasks = [
        {
          id: 'task-1',
          title: 'Task 1',
          description: null,
          status: 'todo',
          priority: 'medium',
          source: 'manual',
          sourceId: 'manual-123',
          ownerId: mockUserId,
          createdAt: new Date(),
          updatedAt: new Date(),
          dueDate: null,
          completedAt: null,
          primaryTagId: null,
          jiraProject: null,
          jiraKey: null,
          jiraType: null,
          tfsProject: null,
          tfsPullRequestId: null,
          tfsRepository: null,
          tfsSourceBranch: null,
          tfsTargetBranch: null,
          assignee: null,
          taskTags: [],
          primaryTag: null,
          _count: { dailyCheckboxes: 0 },
        },
      ];

      vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
      vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);

      const tasks = await service.getTasks(mockUserId);

      expect(tasks).toHaveLength(1);
      expect(prisma.task.findMany).toHaveBeenCalledWith({
        where: { ownerId: mockUserId },
        include: {
          taskTags: {
            include: {
              tag: true,
            },
          },
          primaryTag: true,
          _count: {
            select: {
              dailyCheckboxes: true,
            },
          },
        },
        take: undefined,
        skip: 0,
        orderBy: [
          { completedAt: 'desc' },
          { dueDate: 'asc' },
          { createdAt: 'desc' },
        ],
      });
    });

    it('devrait filtrer par statut', async () => {
      vi.mocked(prisma.task.findMany).mockResolvedValue([]);

      await service.getTasks(mockUserId, { status: ['todo', 'in_progress'] });

      expect(prisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            status: { in: ['todo', 'in_progress'] },
          }),
        })
      );
    });

    it('devrait rechercher dans le titre et la description', async () => {
      vi.mocked(prisma.task.findMany).mockResolvedValue([]);

      await service.getTasks(mockUserId, { search: 'test' });

      expect(prisma.task.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            OR: [
              { title: { contains: 'test' } },
              { description: { contains: 'test' } },
            ],
          }),
        })
      );
    });
  });

  describe('createTask', () => {
    it('devrait créer une nouvelle tâche', async () => {
      const mockTask = {
        id: 'task-1',
        title: 'New Task',
        description: 'Description',
        status: 'todo',
        priority: 'medium',
        ownerId: mockUserId,
        source: 'manual',
        sourceId: 'manual-123',
        createdAt: new Date(),
        updatedAt: new Date(),
        dueDate: null,
        completedAt: null,
        primaryTagId: null,
        jiraProject: null,
        jiraKey: null,
        jiraType: null,
        tfsProject: null,
        tfsPullRequestId: null,
        tfsRepository: null,
        tfsSourceBranch: null,
        tfsTargetBranch: null,
        assignee: null,
        taskTags: [],
        primaryTag: null,
      };

      vi.mocked(prisma.task.create).mockResolvedValue(mockTask as any);
      vi.mocked(prisma.task.findUnique).mockResolvedValue(mockTask as any);
      vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]);
      vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);

      const task = await service.createTask({
        title: 'New Task',
        description: 'Description',
        ownerId: mockUserId,
      });

      expect(task).toBeDefined();
      expect(prisma.task.create).toHaveBeenCalled();
    });

    it('devrait définir completedAt si le statut est done', async () => {
      const mockTask = {
        id: 'task-1',
        title: 'Done Task',
        description: null,
        status: 'done',
        priority: 'medium',
        ownerId: mockUserId,
        source: 'manual',
        sourceId: 'manual-123',
        createdAt: new Date(),
        updatedAt: new Date(),
        dueDate: null,
        completedAt: new Date('2024-01-15'),
        primaryTagId: null,
        jiraProject: null,
        jiraKey: null,
        jiraType: null,
        tfsProject: null,
        tfsPullRequestId: null,
        tfsRepository: null,
        tfsSourceBranch: null,
        tfsTargetBranch: null,
        assignee: null,
        taskTags: [],
        primaryTag: null,
      };

      vi.mocked(prisma.task.create).mockResolvedValue(mockTask as any);
      vi.mocked(prisma.task.findUnique).mockResolvedValue(mockTask as any);
      vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]);
      vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);

      await service.createTask({
        title: 'Done Task',
        status: 'done',
        ownerId: mockUserId,
      });

      expect(prisma.task.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            completedAt: expect.any(Date),
          }),
        })
      );
    });
  });

  describe('updateTask', () => {
    it('devrait mettre à jour une tâche', async () => {
      const mockTask = {
        id: 'task-1',
        title: 'Updated Task',
        description: null,
        status: 'in_progress',
        priority: 'medium',
        ownerId: mockUserId,
        source: 'manual',
        sourceId: 'manual-123',
        createdAt: new Date(),
        updatedAt: new Date(),
        dueDate: null,
        completedAt: null,
        primaryTagId: null,
        jiraProject: null,
        jiraKey: null,
        jiraType: null,
        tfsProject: null,
        tfsPullRequestId: null,
        tfsRepository: null,
        tfsSourceBranch: null,
        tfsTargetBranch: null,
        assignee: null,
        taskTags: [],
        primaryTag: null,
      };

      vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
      vi.mocked(prisma.task.update).mockResolvedValue(mockTask as any);
      vi.mocked(prisma.task.findUnique).mockResolvedValue(mockTask as any);
      vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);

      const task = await service.updateTask(mockUserId, 'task-1', {
        title: 'Updated Task',
        status: 'in_progress',
      });

      expect(task).toBeDefined();
      expect(prisma.task.update).toHaveBeenCalled();
    });

    it('devrait définir completedAt quand le statut passe à done', async () => {
      const mockTask = {
        id: 'task-1',
        title: 'Task',
        description: null,
        status: 'todo',
        priority: 'medium',
        ownerId: mockUserId,
        source: 'manual',
        sourceId: 'manual-123',
        createdAt: new Date(),
        updatedAt: new Date(),
        dueDate: null,
        completedAt: null,
        primaryTagId: null,
        jiraProject: null,
        jiraKey: null,
        jiraType: null,
        tfsProject: null,
        tfsPullRequestId: null,
        tfsRepository: null,
        tfsSourceBranch: null,
        tfsTargetBranch: null,
        assignee: null,
        taskTags: [],
        primaryTag: null,
      };

      vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
      vi.mocked(prisma.task.update).mockResolvedValue({
        ...mockTask,
        status: 'done',
        completedAt: new Date('2024-01-15'),
      } as any);
      vi.mocked(prisma.task.findUnique).mockResolvedValue({
        ...mockTask,
        status: 'done',
        completedAt: new Date('2024-01-15'),
      } as any);
      vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);

      await service.updateTask(mockUserId, 'task-1', {
        status: 'done',
      });

      expect(prisma.task.update).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            completedAt: expect.any(Date),
          }),
        })
      );
    });

    it("devrait lancer une erreur si la tâche n'existe pas", async () => {
      vi.mocked(prisma.task.findFirst).mockResolvedValue(null);

      await expect(
        service.updateTask(mockUserId, 'non-existent', {
          title: 'Updated',
        })
      ).rejects.toThrow('introuvable');
    });
  });

  describe('deleteTask', () => {
    it('devrait supprimer une tâche', async () => {
      const mockTask = {
        id: 'task-1',
        title: 'Task to delete',
        ownerId: mockUserId,
      };

      vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
      vi.mocked(prisma.task.delete).mockResolvedValue({} as any);

      await service.deleteTask(mockUserId, 'task-1');

      expect(prisma.task.delete).toHaveBeenCalledWith({
        where: { id: 'task-1' },
      });
    });

    it("devrait lancer une erreur si la tâche n'existe pas", async () => {
      vi.mocked(prisma.task.findFirst).mockResolvedValue(null);

      await expect(
        service.deleteTask(mockUserId, 'non-existent')
      ).rejects.toThrow('introuvable');
    });
  });

  describe('updateTaskStatus', () => {
    it("devrait mettre à jour le statut d'une tâche", async () => {
      const mockTask = {
        id: 'task-1',
        title: 'Task',
        description: null,
        status: 'todo',
        priority: 'medium',
        ownerId: mockUserId,
        source: 'manual',
        sourceId: 'manual-123',
        createdAt: new Date(),
        updatedAt: new Date(),
        dueDate: null,
        completedAt: null,
        primaryTagId: null,
        jiraProject: null,
        jiraKey: null,
        jiraType: null,
        tfsProject: null,
        tfsPullRequestId: null,
        tfsRepository: null,
        tfsSourceBranch: null,
        tfsTargetBranch: null,
        assignee: null,
        taskTags: [],
        primaryTag: null,
      };

      vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
      vi.mocked(prisma.task.update).mockResolvedValue({
        ...mockTask,
        status: 'in_progress',
      } as any);
      vi.mocked(prisma.task.findUnique).mockResolvedValue({
        ...mockTask,
        status: 'in_progress',
      } as any);
      vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);

      const task = await service.updateTaskStatus(
        mockUserId,
        'task-1',
        'in_progress'
      );

      expect(task).toBeDefined();
      expect(prisma.task.update).toHaveBeenCalled();
    });
  });
});
