import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import { useProjects, useCreateProject, useUpdateProject, useDeleteProject, projectKeys } from '../use-projects';
import { projectService } from '@/lib/services/project.service';
import { toast } from 'sonner';

// Mock the service
vi.mock('@/lib/services/project.service', () => ({
  projectService: {
    getAll: vi.fn(),
    getByUuid: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
  },
}));

describe('use-projects hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('projectKeys', () => {
    it('should generate correct cache keys', () => {
      expect(projectKeys.all).toEqual(['projects']);
      expect(projectKeys.list({ search: 'test' })).toEqual(['projects', 'list', { search: 'test' }]);
      expect(projectKeys.detail('uuid-1')).toEqual(['projects', 'detail', 'uuid-1']);
    });
  });

  describe('useProjects', () => {
    it('should fetch projects successfully', async () => {
      const mockData = [
        { id: 1, name: 'Project Alpha', code: 'P-001' },
        { id: 2, name: 'Project Beta', code: 'P-002' },
      ];

      vi.mocked(projectService.getAll).mockResolvedValue(mockData);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useProjects({ search: 'test' }), { wrapper });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data).toEqual(mockData);
      expect(projectService.getAll).toHaveBeenCalledWith({ search: 'test' });
    });

    it('should fetch projects without params', async () => {
      const mockData = [{ id: 1, name: 'Project Alpha' }];
      vi.mocked(projectService.getAll).mockResolvedValue(mockData);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useProjects(), { wrapper });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(projectService.getAll).toHaveBeenCalledWith(undefined);
    });

    it('should handle error state', async () => {
      vi.mocked(projectService.getAll).mockRejectedValue(new Error('API Error'));

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useProjects({}), { wrapper });

      await waitFor(() => {
        expect(result.current.isError).toBe(true);
      });
    });
  });

  describe('useCreateProject', () => {
    it('should create project and show success toast', async () => {
      const mockResponse = { id: 1, name: 'New Project' };
      vi.mocked(projectService.create).mockResolvedValue(mockResponse);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useCreateProject(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          name: 'New Project',
          code: 'P-003',
          contractId: 1,
        });
      });

      expect(projectService.create).toHaveBeenCalledWith({
        name: 'New Project',
        code: 'P-003',
        contractId: 1,
      });
      expect(toast.success).toHaveBeenCalledWith('Project created successfully');
    });

    it('should show error toast on failure', async () => {
      const mockError = {
        message: 'Error',
        response: { data: { message: 'Duplicate code' } },
      };
      vi.mocked(projectService.create).mockRejectedValue(mockError);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useCreateProject(), { wrapper });

      await act(async () => {
        try {
          await result.current.mutateAsync({
            name: 'Test',
            code: 'P-001',
          });
        } catch {
          // Expected
        }
      });

      expect(toast.error).toHaveBeenCalledWith('Failed to create project', {
        description: 'Duplicate code',
      });
    });
  });

  describe('useUpdateProject', () => {
    it('should update project and show success toast', async () => {
      const mockResponse = { id: 1, name: 'Updated Project' };
      vi.mocked(projectService.update).mockResolvedValue(mockResponse);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useUpdateProject(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          uuid: 'uuid-1',
          data: { name: 'Updated Project' },
        });
      });

      expect(projectService.update).toHaveBeenCalledWith('uuid-1', { name: 'Updated Project' });
      expect(toast.success).toHaveBeenCalledWith('Project updated successfully');
    });

    it('should show error toast on failure', async () => {
      const mockError = {
        message: 'Error',
        response: { data: { message: 'Not found' } },
      };
      vi.mocked(projectService.update).mockRejectedValue(mockError);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useUpdateProject(), { wrapper });

      await act(async () => {
        try {
          await result.current.mutateAsync({
            uuid: 'uuid-999',
            data: { name: 'Test' },
          });
        } catch {
          // Expected
        }
      });

      expect(toast.error).toHaveBeenCalledWith('Failed to update project', {
        description: 'Not found',
      });
    });
  });

  describe('useDeleteProject', () => {
    it('should delete project and show success toast', async () => {
      vi.mocked(projectService.delete).mockResolvedValue({});

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useDeleteProject(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync('uuid-1');
      });

      expect(projectService.delete).toHaveBeenCalledWith('uuid-1');
      expect(toast.success).toHaveBeenCalledWith('Project deleted successfully');
    });

    it('should show error toast on delete failure', async () => {
      const mockError = {
        message: 'Error',
        response: { data: { message: 'Cannot delete' } },
      };
      vi.mocked(projectService.delete).mockRejectedValue(mockError);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useDeleteProject(), { wrapper });

      await act(async () => {
        try {
          await result.current.mutateAsync('uuid-1');
        } catch {
          // Expected
        }
      });

      expect(toast.error).toHaveBeenCalledWith('Failed to delete project', {
        description: 'Cannot delete',
      });
    });
  });
});
