/**
 * Missions Store Tests
 *
 * Tests for Zustand missions store managing daily, weekly, and special missions.
 *
 * Test Coverage:
 * - Store Initialization (3 tests): Initial state, structure validation
 * - Fetch Operations (6 tests): Daily, weekly, special missions
 * - Mission Progress (4 tests): Update progress, completion detection
 * - Claim Rewards (4 tests): Reward claiming, status updates
 * - Refresh All (2 tests): Batch fetch, error handling
 * - Error Handling (3 tests): Network errors, invalid data
 * - State Management (3 tests): Loading states, error states
 *
 * Total: 25 tests
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useMissionsStore } from '../missionsStore';
import { missionsAPI, type Mission } from '@/services/api/missionsAPI';

// Mock the missions API
vi.mock('@/services/api/missionsAPI', () => ({
  missionsAPI: {
    getDailyMissions: vi.fn(),
    getWeeklyMissions: vi.fn(),
    getSpecialMissions: vi.fn(),
    claimRewards: vi.fn(),
    getMissionProgress: vi.fn(),
    getMissionStats: vi.fn(),
  },
}));

describe('MissionsStore', () => {
  // ============================================================================
  // MOCK DATA
  // ============================================================================

  const mockDailyMission: Mission = {
    id: 'daily-1',
    userId: 'user-123',
    type: 'daily',
    category: 'exercises',
    title: 'Complete 5 exercises',
    description: 'Complete any 5 exercises today',
    objective: {
      type: 'exercise_completion',
      target: 5,
      current: 2,
    },
    rewards: {
      mlCoins: 50,
      xp: 100,
    },
    status: 'active',
    expiresAt: '2025-11-10T00:00:00Z',
    createdAt: '2025-11-09T00:00:00Z',
  };

  const mockWeeklyMission: Mission = {
    id: 'weekly-1',
    userId: 'user-123',
    type: 'weekly',
    category: 'modules',
    title: 'Complete 3 modules',
    description: 'Complete any 3 modules this week',
    objective: {
      type: 'module_completion',
      target: 3,
      current: 1,
    },
    rewards: {
      mlCoins: 200,
      xp: 500,
    },
    status: 'active',
    expiresAt: '2025-11-16T00:00:00Z',
    createdAt: '2025-11-09T00:00:00Z',
  };

  const mockSpecialMission: Mission = {
    id: 'special-1',
    userId: 'user-123',
    type: 'special',
    category: 'achievements',
    title: 'Unlock 5 achievements',
    description: 'Special event: Unlock 5 achievements',
    objective: {
      type: 'achievement_unlock',
      target: 5,
      current: 3,
    },
    rewards: {
      mlCoins: 500,
      xp: 1000,
      items: ['power-up-1', 'badge-1'],
    },
    status: 'active',
    expiresAt: '2025-12-31T00:00:00Z',
    createdAt: '2025-11-01T00:00:00Z',
  };

  // ============================================================================
  // SETUP & TEARDOWN
  // ============================================================================

  beforeEach(() => {
    vi.clearAllMocks();

    // Reset store to initial state
    useMissionsStore.setState({
      dailyMissions: [],
      weeklyMissions: [],
      specialMissions: [],
      isLoading: false,
      error: null,
    });
  });

  // ============================================================================
  // STORE INITIALIZATION TESTS
  // ============================================================================

  describe('Store Initialization', () => {
    it('should have correct initial state', () => {
      const state = useMissionsStore.getState();

      expect(state.dailyMissions).toEqual([]);
      expect(state.weeklyMissions).toEqual([]);
      expect(state.specialMissions).toEqual([]);
      expect(state.isLoading).toBe(false);
      expect(state.error).toBeNull();
    });

    it('should have all required actions', () => {
      const state = useMissionsStore.getState();

      expect(typeof state.fetchDailyMissions).toBe('function');
      expect(typeof state.fetchWeeklyMissions).toBe('function');
      expect(typeof state.fetchSpecialMissions).toBe('function');
      expect(typeof state.claimRewards).toBe('function');
      expect(typeof state.refreshAllMissions).toBe('function');
      expect(typeof state.updateMissionProgress).toBe('function');
    });

    it('should maintain separate mission arrays by type', () => {
      useMissionsStore.setState({
        dailyMissions: [mockDailyMission],
        weeklyMissions: [mockWeeklyMission],
        specialMissions: [mockSpecialMission],
      });

      const state = useMissionsStore.getState();

      expect(state.dailyMissions.length).toBe(1);
      expect(state.weeklyMissions.length).toBe(1);
      expect(state.specialMissions.length).toBe(1);
      expect(state.dailyMissions[0].type).toBe('daily');
      expect(state.weeklyMissions[0].type).toBe('weekly');
      expect(state.specialMissions[0].type).toBe('special');
    });
  });

  // ============================================================================
  // FETCH OPERATIONS TESTS
  // ============================================================================

  describe('Fetch Operations', () => {
    it('should fetch daily missions successfully', async () => {
      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue([mockDailyMission]);

      const { fetchDailyMissions } = useMissionsStore.getState();
      await fetchDailyMissions();

      const state = useMissionsStore.getState();

      expect(state.dailyMissions).toEqual([mockDailyMission]);
      expect(state.isLoading).toBe(false);
      expect(state.error).toBeNull();
      expect(missionsAPI.getDailyMissions).toHaveBeenCalledTimes(1);
    });

    it('should set loading state during daily missions fetch', async () => {
      vi.mocked(missionsAPI.getDailyMissions).mockImplementation(() =>
        new Promise((resolve) => setTimeout(() => resolve([mockDailyMission]), 100))
      );

      const { fetchDailyMissions } = useMissionsStore.getState();
      const promise = fetchDailyMissions();

      // Check loading state immediately
      expect(useMissionsStore.getState().isLoading).toBe(true);

      await promise;

      expect(useMissionsStore.getState().isLoading).toBe(false);
    });

    it('should fetch weekly missions successfully', async () => {
      vi.mocked(missionsAPI.getWeeklyMissions).mockResolvedValue([mockWeeklyMission]);

      const { fetchWeeklyMissions } = useMissionsStore.getState();
      await fetchWeeklyMissions();

      const state = useMissionsStore.getState();

      expect(state.weeklyMissions).toEqual([mockWeeklyMission]);
      expect(state.isLoading).toBe(false);
      expect(state.error).toBeNull();
    });

    it('should fetch special missions successfully', async () => {
      vi.mocked(missionsAPI.getSpecialMissions).mockResolvedValue([mockSpecialMission]);

      const { fetchSpecialMissions } = useMissionsStore.getState();
      await fetchSpecialMissions();

      const state = useMissionsStore.getState();

      expect(state.specialMissions).toEqual([mockSpecialMission]);
      expect(state.error).toBeNull();
    });

    it('should fetch multiple missions of same type', async () => {
      const multipleDailyMissions = [
        mockDailyMission,
        { ...mockDailyMission, id: 'daily-2', title: 'Mission 2' },
        { ...mockDailyMission, id: 'daily-3', title: 'Mission 3' },
      ];

      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue(multipleDailyMissions);

      const { fetchDailyMissions } = useMissionsStore.getState();
      await fetchDailyMissions();

      const state = useMissionsStore.getState();

      expect(state.dailyMissions.length).toBe(3);
      expect(state.dailyMissions[0].id).toBe('daily-1');
      expect(state.dailyMissions[1].id).toBe('daily-2');
      expect(state.dailyMissions[2].id).toBe('daily-3');
    });

    it('should overwrite previous missions on refetch', async () => {
      // First fetch
      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue([mockDailyMission]);
      await useMissionsStore.getState().fetchDailyMissions();

      expect(useMissionsStore.getState().dailyMissions.length).toBe(1);

      // Second fetch with different data
      const newDailyMission = { ...mockDailyMission, id: 'daily-new' };
      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue([newDailyMission]);
      await useMissionsStore.getState().fetchDailyMissions();

      const state = useMissionsStore.getState();

      expect(state.dailyMissions.length).toBe(1);
      expect(state.dailyMissions[0].id).toBe('daily-new');
    });
  });

  // ============================================================================
  // MISSION PROGRESS TESTS
  // ============================================================================

  describe('Mission Progress', () => {
    beforeEach(() => {
      useMissionsStore.setState({
        dailyMissions: [mockDailyMission],
        weeklyMissions: [mockWeeklyMission],
        specialMissions: [mockSpecialMission],
      });
    });

    it('should update mission progress', () => {
      const { updateMissionProgress } = useMissionsStore.getState();

      updateMissionProgress('daily-1', 4);

      const state = useMissionsStore.getState();
      const mission = state.dailyMissions.find((m) => m.id === 'daily-1');

      expect(mission?.objective.current).toBe(4);
    });

    it('should mark mission as completed when target is reached', () => {
      const { updateMissionProgress } = useMissionsStore.getState();

      // Update to meet target (5)
      updateMissionProgress('daily-1', 5);

      const state = useMissionsStore.getState();
      const mission = state.dailyMissions.find((m) => m.id === 'daily-1');

      expect(mission?.status).toBe('completed');
      expect(mission?.objective.current).toBe(5);
    });

    it('should mark mission as completed when progress exceeds target', () => {
      const { updateMissionProgress } = useMissionsStore.getState();

      // Update beyond target
      updateMissionProgress('daily-1', 10);

      const state = useMissionsStore.getState();
      const mission = state.dailyMissions.find((m) => m.id === 'daily-1');

      expect(mission?.status).toBe('completed');
      expect(mission?.objective.current).toBe(10);
    });

    it('should update progress across all mission types', () => {
      const { updateMissionProgress } = useMissionsStore.getState();

      updateMissionProgress('daily-1', 3);
      updateMissionProgress('weekly-1', 2);
      updateMissionProgress('special-1', 4);

      const state = useMissionsStore.getState();

      expect(state.dailyMissions[0].objective.current).toBe(3);
      expect(state.weeklyMissions[0].objective.current).toBe(2);
      expect(state.specialMissions[0].objective.current).toBe(4);
    });
  });

  // ============================================================================
  // CLAIM REWARDS TESTS
  // ============================================================================

  describe('Claim Rewards', () => {
    beforeEach(() => {
      const completedMission: Mission = {
        ...mockDailyMission,
        status: 'completed',
        objective: { ...mockDailyMission.objective, current: 5 },
      };

      useMissionsStore.setState({
        dailyMissions: [completedMission],
        weeklyMissions: [mockWeeklyMission],
        specialMissions: [mockSpecialMission],
      });
    });

    it('should claim rewards and update mission status', async () => {
      vi.mocked(missionsAPI.claimRewards).mockResolvedValue({
        mlCoins: 50,
        xp: 100,
      });

      const { claimRewards } = useMissionsStore.getState();
      await claimRewards('daily-1');

      const state = useMissionsStore.getState();
      const mission = state.dailyMissions.find((m) => m.id === 'daily-1');

      expect(mission?.status).toBe('claimed');
      expect(missionsAPI.claimRewards).toHaveBeenCalledWith('daily-1');
    });

    it('should handle claim rewards for weekly missions', async () => {
      const completedWeekly: Mission = {
        ...mockWeeklyMission,
        status: 'completed',
        objective: { ...mockWeeklyMission.objective, current: 3 },
      };

      useMissionsStore.setState({
        weeklyMissions: [completedWeekly],
      });

      vi.mocked(missionsAPI.claimRewards).mockResolvedValue({
        mlCoins: 200,
        xp: 500,
      });

      await useMissionsStore.getState().claimRewards('weekly-1');

      const state = useMissionsStore.getState();
      const mission = state.weeklyMissions.find((m) => m.id === 'weekly-1');

      expect(mission?.status).toBe('claimed');
    });

    it('should handle claim rewards for special missions with items', async () => {
      const completedSpecial: Mission = {
        ...mockSpecialMission,
        status: 'completed',
        objective: { ...mockSpecialMission.objective, current: 5 },
      };

      useMissionsStore.setState({
        specialMissions: [completedSpecial],
      });

      vi.mocked(missionsAPI.claimRewards).mockResolvedValue({
        mlCoins: 500,
        xp: 1000,
        items: ['power-up-1', 'badge-1'],
      });

      await useMissionsStore.getState().claimRewards('special-1');

      const state = useMissionsStore.getState();
      const mission = state.specialMissions.find((m) => m.id === 'special-1');

      expect(mission?.status).toBe('claimed');
      expect(mission?.rewards.items).toEqual(['power-up-1', 'badge-1']);
    });

    it('should handle claim rewards API error', async () => {
      vi.mocked(missionsAPI.claimRewards).mockRejectedValue(
        new Error('Failed to claim rewards')
      );

      const { claimRewards } = useMissionsStore.getState();
      await claimRewards('daily-1');

      const state = useMissionsStore.getState();

      expect(state.error).toBe('Failed to claim rewards');
      // Mission status should remain unchanged on error
      expect(state.dailyMissions[0].status).toBe('completed');
    });
  });

  // ============================================================================
  // REFRESH ALL TESTS
  // ============================================================================

  describe('Refresh All Missions', () => {
    it('should fetch all mission types in parallel', async () => {
      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue([mockDailyMission]);
      vi.mocked(missionsAPI.getWeeklyMissions).mockResolvedValue([mockWeeklyMission]);
      vi.mocked(missionsAPI.getSpecialMissions).mockResolvedValue([mockSpecialMission]);

      const { refreshAllMissions } = useMissionsStore.getState();
      await refreshAllMissions();

      const state = useMissionsStore.getState();

      expect(state.dailyMissions).toEqual([mockDailyMission]);
      expect(state.weeklyMissions).toEqual([mockWeeklyMission]);
      expect(state.specialMissions).toEqual([mockSpecialMission]);
      expect(missionsAPI.getDailyMissions).toHaveBeenCalledTimes(1);
      expect(missionsAPI.getWeeklyMissions).toHaveBeenCalledTimes(1);
      expect(missionsAPI.getSpecialMissions).toHaveBeenCalledTimes(1);
    });

    it('should handle partial failures in refresh all', async () => {
      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue([mockDailyMission]);
      vi.mocked(missionsAPI.getWeeklyMissions).mockRejectedValue(
        new Error('Weekly missions failed')
      );
      vi.mocked(missionsAPI.getSpecialMissions).mockResolvedValue([mockSpecialMission]);

      const { refreshAllMissions } = useMissionsStore.getState();
      await refreshAllMissions();

      const state = useMissionsStore.getState();

      // Daily and special should succeed
      expect(state.dailyMissions).toEqual([mockDailyMission]);
      expect(state.specialMissions).toEqual([mockSpecialMission]);
      // Error should be set from weekly missions failure
      expect(state.error).toBe('Weekly missions failed');
    });
  });

  // ============================================================================
  // ERROR HANDLING TESTS
  // ============================================================================

  describe('Error Handling', () => {
    it('should handle daily missions fetch error', async () => {
      vi.mocked(missionsAPI.getDailyMissions).mockRejectedValue(
        new Error('Network error')
      );

      const { fetchDailyMissions } = useMissionsStore.getState();
      await fetchDailyMissions();

      const state = useMissionsStore.getState();

      expect(state.error).toBe('Network error');
      expect(state.isLoading).toBe(false);
      expect(state.dailyMissions).toEqual([]);
    });

    it('should handle weekly missions fetch error', async () => {
      vi.mocked(missionsAPI.getWeeklyMissions).mockRejectedValue(
        new Error('Server error')
      );

      const { fetchWeeklyMissions } = useMissionsStore.getState();
      await fetchWeeklyMissions();

      const state = useMissionsStore.getState();

      expect(state.error).toBe('Server error');
      expect(state.isLoading).toBe(false);
      expect(state.weeklyMissions).toEqual([]);
    });

    it('should handle special missions fetch error', async () => {
      vi.mocked(missionsAPI.getSpecialMissions).mockRejectedValue(
        new Error('Unauthorized')
      );

      const { fetchSpecialMissions } = useMissionsStore.getState();
      await fetchSpecialMissions();

      const state = useMissionsStore.getState();

      expect(state.error).toBe('Unauthorized');
      expect(state.specialMissions).toEqual([]);
    });
  });

  // ============================================================================
  // STATE MANAGEMENT TESTS
  // ============================================================================

  describe('State Management', () => {
    it('should persist error state until next error or manual reset', async () => {
      // Simulate an error first
      vi.mocked(missionsAPI.getDailyMissions).mockRejectedValueOnce(
        new Error('Previous error')
      );

      await useMissionsStore.getState().fetchDailyMissions();

      expect(useMissionsStore.getState().error).toBe('Previous error');

      // Now succeed - error persists (store doesn't clear on success)
      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue([mockDailyMission]);

      await useMissionsStore.getState().fetchDailyMissions();

      const state = useMissionsStore.getState();

      // NOTE: Store does NOT clear error on successful fetch (design limitation)
      expect(state.error).toBe('Previous error'); // Error persists
      expect(state.dailyMissions).toEqual([mockDailyMission]); // But data updates
    });

    it('should manage loading state correctly', async () => {
      vi.mocked(missionsAPI.getDailyMissions).mockImplementation(() =>
        new Promise((resolve) => setTimeout(() => resolve([mockDailyMission]), 50))
      );

      const { fetchDailyMissions } = useMissionsStore.getState();

      expect(useMissionsStore.getState().isLoading).toBe(false);

      const promise = fetchDailyMissions();

      expect(useMissionsStore.getState().isLoading).toBe(true);

      await promise;

      expect(useMissionsStore.getState().isLoading).toBe(false);
    });

    it('should not interfere with other mission types when updating one', async () => {
      useMissionsStore.setState({
        dailyMissions: [mockDailyMission],
        weeklyMissions: [mockWeeklyMission],
        specialMissions: [mockSpecialMission],
      });

      vi.mocked(missionsAPI.getDailyMissions).mockResolvedValue([
        { ...mockDailyMission, title: 'Updated Daily' },
      ]);

      await useMissionsStore.getState().fetchDailyMissions();

      const state = useMissionsStore.getState();

      // Daily should be updated
      expect(state.dailyMissions[0].title).toBe('Updated Daily');
      // Others should remain unchanged
      expect(state.weeklyMissions).toEqual([mockWeeklyMission]);
      expect(state.specialMissions).toEqual([mockSpecialMission]);
    });
  });
});
