import { choreService } from '../choreService'; // Adjust path
import { api } from '../api'; // Actual axios instance from api.ts
import { groupService } from '../groupService';
import type { Chore, ChoreCreate, ChoreUpdate, ChoreType } from '../../types/chore'; // Adjust path
import type { Group } from '../groupService';

// Mock the api module (axios instance)
vi.mock('../api', () => ({
  api: {
    get: vi.fn(),
    post: vi.fn(),
    put: vi.fn(),
    delete: vi.fn(),
  },
}));

// Mock groupService
vi.mock('../groupService', () => ({
  groupService: {
    getUserGroups: vi.fn(),
  },
}));

const mockApi = api as vi.Mocked<typeof api>;
const mockGroupService = groupService as vi.Mocked<typeof groupService>;

describe('Chore Service', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  const mockPersonalChore: Chore = { id: 1, name: 'Personal Task 1', type: 'personal', description: '' };
  const mockGroupChore: Chore = { id: 2, name: 'Group Task 1', type: 'group', group_id: 10, description: '' };

  describe('getPersonalChores', () => {
    it('should fetch personal chores successfully', async () => {
      mockApi.get.mockResolvedValue({ data: [mockPersonalChore] });
      const chores = await choreService.getPersonalChores();
      expect(mockApi.get).toHaveBeenCalledWith('/api/v1/chores/personal');
      expect(chores).toEqual([mockPersonalChore]);
    });

    it('should propagate error on failure', async () => {
      const error = new Error('Failed to fetch');
      mockApi.get.mockRejectedValue(error);
      await expect(choreService.getPersonalChores()).rejects.toThrow(error);
    });
  });

  describe('getChores (group chores)', () => {
    const groupId = 10;
    it('should fetch chores for a specific group successfully', async () => {
      mockApi.get.mockResolvedValue({ data: [mockGroupChore] });
      const chores = await choreService.getChores(groupId);
      expect(mockApi.get).toHaveBeenCalledWith(`/api/v1/chores/groups/${groupId}/chores`);
      expect(chores).toEqual([mockGroupChore]);
    });

    it('should propagate error on failure', async () => {
      const error = new Error('Failed to fetch group chores');
      mockApi.get.mockRejectedValue(error);
      await expect(choreService.getChores(groupId)).rejects.toThrow(error);
    });
  });

  describe('createChore', () => {
    it('should create a personal chore', async () => {
      const newPersonalChore: ChoreCreate = { name: 'New Personal', type: 'personal', description: 'desc' };
      const createdChore = { ...newPersonalChore, id: 3 };
      mockApi.post.mockResolvedValue({ data: createdChore });

      const result = await choreService.createChore(newPersonalChore);
      expect(mockApi.post).toHaveBeenCalledWith('/api/v1/chores/personal', newPersonalChore);
      expect(result).toEqual(createdChore);
    });

    it('should create a group chore', async () => {
      const newGroupChore: ChoreCreate = { name: 'New Group', type: 'group', group_id: 10, description: 'desc' };
      const createdChore = { ...newGroupChore, id: 4 };
      mockApi.post.mockResolvedValue({ data: createdChore });

      const result = await choreService.createChore(newGroupChore);
      expect(mockApi.post).toHaveBeenCalledWith(`/api/v1/chores/groups/${newGroupChore.group_id}/chores`, newGroupChore);
      expect(result).toEqual(createdChore);
    });

    it('should throw error for invalid type or missing group_id for group chore', async () => {
      // @ts-expect-error testing invalid type
      const invalidTypeChore: ChoreCreate = { name: 'Invalid', type: 'unknown' };
      await expect(choreService.createChore(invalidTypeChore)).rejects.toThrow('Invalid chore type or missing group_id for group chore');

      const missingGroupIdChore: ChoreCreate = { name: 'Missing GroupId', type: 'group' };
      await expect(choreService.createChore(missingGroupIdChore)).rejects.toThrow('Invalid chore type or missing group_id for group chore');
    });

    it('should propagate API error on failure', async () => {
      const newChore: ChoreCreate = { name: 'Test', type: 'personal' };
      const error = new Error('API Create Failed');
      mockApi.post.mockRejectedValue(error);
      await expect(choreService.createChore(newChore)).rejects.toThrow(error);
    });
  });

  describe('updateChore', () => {
    const choreId = 1;
    it('should update a personal chore', async () => {
      const updatedPersonalChoreData: ChoreUpdate = { name: 'Updated Personal', type: 'personal', description: 'new desc' };
      const responseChore = { ...updatedPersonalChoreData, id: choreId };
      mockApi.put.mockResolvedValue({ data: responseChore });

      const result = await choreService.updateChore(choreId, updatedPersonalChoreData);
      expect(mockApi.put).toHaveBeenCalledWith(`/api/v1/chores/personal/${choreId}`, updatedPersonalChoreData);
      expect(result).toEqual(responseChore);
    });

    it('should update a group chore', async () => {
      const updatedGroupChoreData: ChoreUpdate = { name: 'Updated Group', type: 'group', group_id: 10, description: 'new desc' };
      const responseChore = { ...updatedGroupChoreData, id: choreId };
      mockApi.put.mockResolvedValue({ data: responseChore });

      const result = await choreService.updateChore(choreId, updatedGroupChoreData);
      expect(mockApi.put).toHaveBeenCalledWith(`/api/v1/chores/groups/${updatedGroupChoreData.group_id}/chores/${choreId}`, updatedGroupChoreData);
      expect(result).toEqual(responseChore);
    });
    
    it('should throw error for invalid type or missing group_id for group chore update', async () => {
        // @ts-expect-error testing invalid type
        const invalidTypeUpdate: ChoreUpdate = { name: 'Invalid', type: 'unknown' };
        await expect(choreService.updateChore(choreId, invalidTypeUpdate)).rejects.toThrow('Invalid chore type or missing group_id for group chore update');
  
        const missingGroupIdUpdate: ChoreUpdate = { name: 'Missing GroupId', type: 'group' };
        await expect(choreService.updateChore(choreId, missingGroupIdUpdate)).rejects.toThrow('Invalid chore type or missing group_id for group chore update');
      });

    it('should propagate API error on failure', async () => {
      const updatedChoreData: ChoreUpdate = { name: 'Test Update', type: 'personal' };
      const error = new Error('API Update Failed');
      mockApi.put.mockRejectedValue(error);
      await expect(choreService.updateChore(choreId, updatedChoreData)).rejects.toThrow(error);
    });
  });

  describe('deleteChore', () => {
    const choreId = 1;
    it('should delete a personal chore', async () => {
      mockApi.delete.mockResolvedValue({ data: null }); // delete often returns no content
      await choreService.deleteChore(choreId, 'personal');
      expect(mockApi.delete).toHaveBeenCalledWith(`/api/v1/chores/personal/${choreId}`);
    });

    it('should delete a group chore', async () => {
      const groupId = 10;
      mockApi.delete.mockResolvedValue({ data: null });
      await choreService.deleteChore(choreId, 'group', groupId);
      expect(mockApi.delete).toHaveBeenCalledWith(`/api/v1/chores/groups/${groupId}/chores/${choreId}`);
    });

    it('should throw error for invalid type or missing group_id for group chore deletion', async () => {
        // @ts-expect-error testing invalid type
        await expect(choreService.deleteChore(choreId, 'unknown')).rejects.toThrow('Invalid chore type or missing group_id for group chore deletion');
  
        await expect(choreService.deleteChore(choreId, 'group')).rejects.toThrow('Invalid chore type or missing group_id for group chore deletion'); // Missing groupId
      });

    it('should propagate API error on failure', async () => {
      const error = new Error('API Delete Failed');
      mockApi.delete.mockRejectedValue(error);
      await expect(choreService.deleteChore(choreId, 'personal')).rejects.toThrow(error);
    });
  });

  describe('getAllChores', () => {
    const mockUserGroups: Group[] = [
      { id: 1, name: 'Family', members: [], owner_id: 1 },
      { id: 2, name: 'Work', members: [], owner_id: 1 },
    ];
    const mockPersonalChores: Chore[] = [{ id: 100, name: 'My Laundry', type: 'personal', description:'' }];
    const mockFamilyChores: Chore[] = [{ id: 101, name: 'Clean Kitchen', type: 'group', group_id: 1, description:'' }];
    const mockWorkChores: Chore[] = [{ id: 102, name: 'Team Meeting Prep', type: 'group', group_id: 2, description:'' }];

    beforeEach(() => {
        // Mock the direct API calls made by getPersonalChores and getChores
        // if we are not spying/mocking those specific choreService methods.
        // Here, we assume choreService.getPersonalChores and choreService.getChores are called,
        // so we can mock them or let them call the mocked api. For simplicity, let them call mocked api.
      });

    it('should fetch all personal and group chores successfully', async () => {
      mockApi.get
        .mockResolvedValueOnce({ data: mockPersonalChores }) // For getPersonalChores
        .mockResolvedValueOnce({ data: mockFamilyChores })   // For getChores(group1)
        .mockResolvedValueOnce({ data: mockWorkChores });  // For getChores(group2)
      mockGroupService.getUserGroups.mockResolvedValue(mockUserGroups);

      const allChores = await choreService.getAllChores();

      expect(mockApi.get).toHaveBeenCalledWith('/api/v1/chores/personal');
      expect(mockGroupService.getUserGroups).toHaveBeenCalled();
      expect(mockApi.get).toHaveBeenCalledWith(`/api/v1/chores/groups/${mockUserGroups[0].id}/chores`);
      expect(mockApi.get).toHaveBeenCalledWith(`/api/v1/chores/groups/${mockUserGroups[1].id}/chores`);
      expect(allChores).toEqual([...mockPersonalChores, ...mockFamilyChores, ...mockWorkChores]);
    });

    it('should return partial results if fetching chores for one group fails', async () => {
        const groupFetchError = new Error('Failed to fetch group 2 chores');
        mockApi.get
          .mockResolvedValueOnce({ data: mockPersonalChores }) // Personal chores
          .mockResolvedValueOnce({ data: mockFamilyChores })   // Group 1 chores
          .mockRejectedValueOnce(groupFetchError);            // Group 2 chores fail
        mockGroupService.getUserGroups.mockResolvedValue(mockUserGroups);
        const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
  
        const allChores = await choreService.getAllChores();
  
        expect(allChores).toEqual([...mockPersonalChores, ...mockFamilyChores]);
        expect(consoleErrorSpy).toHaveBeenCalledWith(`Failed to get chores for group ${mockUserGroups[1].id} (${mockUserGroups[1].name}):`, groupFetchError);
        consoleErrorSpy.mockRestore();
      });
      
      it('should propagate error if getPersonalChores fails', async () => {
        const personalFetchError = new Error('Failed to fetch personal chores');
        mockApi.get.mockRejectedValueOnce(personalFetchError); // getPersonalChores fails
        mockGroupService.getUserGroups.mockResolvedValue(mockUserGroups); // This might not even be called
  
        await expect(choreService.getAllChores()).rejects.toThrow(personalFetchError);
        expect(mockGroupService.getUserGroups).not.toHaveBeenCalled(); // Or it might, depending on Promise.all behavior if not used
      });

      it('should propagate error if getUserGroups fails', async () => {
        const groupsFetchError = new Error('Failed to fetch groups');
        // getPersonalChores might succeed or fail, let's say it succeeds for this test
        mockApi.get.mockResolvedValueOnce({ data: mockPersonalChores }); 
        mockGroupService.getUserGroups.mockRejectedValue(groupsFetchError);
  
        await expect(choreService.getAllChores()).rejects.toThrow(groupsFetchError);
      });
  });
});
