import activeSessionReducer, {
  fetchActiveSession,
  joinActiveSession,
  leaveActiveSession,
  setSessionId,
  setGuardsPassed,
  setSessionRules,
  setSubscriptionRules,
  setConnectionStatus,
  addParticipant,
  removeParticipant,
  updateParticipant,
  setUserTracks,
  addUserTrack,
  removeUserTrack,
  setSelectedJamTrack,
  setJamTrackStems,
  setBackingTrackData,
  clearBackingTrackData,
  startRecording,
  stopRecording,
  updateSessionData,
  clearSession,
  selectActiveSession,
  selectSessionId,
  selectInSession,
  selectSessionRules,
  selectSubscriptionRules,
  selectJoinStatus,
  selectHasJoined,
  selectGuardsPassed,
  selectUserTracks,
  selectSelectedJamTrack,
  selectJamTrackStems,
  selectBackingTrackData,
  selectConnectionStatus,
  selectIsRecording
} from '../activeSessionSlice';

// Mock the API functions
jest.mock('../../../helpers/rest', () => ({
  getSessionHistory: jest.fn(),
  joinSession: jest.fn(),
  leaveSession: jest.fn()
}));

import { getSessionHistory, joinSession as apiJoinSession, leaveSession as apiLeaveSession } from '../../../helpers/rest';

describe('activeSessionSlice', () => {
  const initialState = {
    sessionId: null,
    sessionData: null,
    sessionRules: null,
    subscriptionRules: null,
    joinStatus: 'idle',
    guardsPassed: false,
    hasJoined: false,
    participants: [],
    userTracks: [],
    jamTracks: [],
    backingTracks: [],
    selectedJamTrack: null,
    jamTrackStems: [],
    backingTrackData: null,
    recordingState: {
      isRecording: false,
      recordingId: null,
      recordedTracks: []
    },
    connectionStatus: 'disconnected',
    showConnectionAlert: false,
    loading: false,
    error: null
  };

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('reducer', () => {
    it('should return the initial state', () => {
      expect(activeSessionReducer(undefined, { type: 'unknown' })).toEqual(initialState);
    });
  });

  describe('synchronous actions', () => {
    it('should handle setSessionId', () => {
      const actual = activeSessionReducer(initialState, setSessionId('session-123'));
      expect(actual.sessionId).toBe('session-123');
    });

    it('should handle setGuardsPassed', () => {
      const actual = activeSessionReducer(initialState, setGuardsPassed(true));
      expect(actual.guardsPassed).toBe(true);
    });

    it('should handle setConnectionStatus - disconnected', () => {
      const actual = activeSessionReducer(initialState, setConnectionStatus('disconnected'));
      expect(actual.connectionStatus).toBe('disconnected');
      expect(actual.showConnectionAlert).toBe(true);
    });

    it('should handle setConnectionStatus - connected', () => {
      const stateWithAlert = {
        ...initialState,
        connectionStatus: 'disconnected',
        showConnectionAlert: true
      };
      const actual = activeSessionReducer(stateWithAlert, setConnectionStatus('connected'));
      expect(actual.connectionStatus).toBe('connected');
      expect(actual.showConnectionAlert).toBe(false);
    });

    it('should handle clearSession', () => {
      const stateWithData = {
        ...initialState,
        sessionId: 'session-123',
        sessionData: { id: 'session-123', name: 'Test Session' },
        hasJoined: true,
        guardsPassed: true,
        userTracks: [{ id: 1, name: 'Track 1' }],
        participants: [{ id: 1, name: 'User 1' }]
      };

      const actual = activeSessionReducer(stateWithData, clearSession());
      expect(actual).toEqual(initialState);
    });
  });

  describe('participant actions', () => {
    it('should handle addParticipant', () => {
      const participant = { id: 1, name: 'John Doe' };
      const actual = activeSessionReducer(initialState, addParticipant(participant));

      expect(actual.participants).toHaveLength(1);
      expect(actual.participants[0]).toEqual(participant);
    });

    it('should not add duplicate participant', () => {
      const participant = { id: 1, name: 'John Doe' };
      let state = activeSessionReducer(initialState, addParticipant(participant));
      state = activeSessionReducer(state, addParticipant(participant));

      expect(state.participants).toHaveLength(1);
    });

    it('should handle removeParticipant', () => {
      const stateWithParticipants = {
        ...initialState,
        participants: [
          { id: 1, name: 'John' },
          { id: 2, name: 'Jane' }
        ]
      };

      const actual = activeSessionReducer(stateWithParticipants, removeParticipant(1));
      expect(actual.participants).toHaveLength(1);
      expect(actual.participants[0].id).toBe(2);
    });

    it('should handle updateParticipant', () => {
      const stateWithParticipants = {
        ...initialState,
        participants: [
          { id: 1, name: 'John', status: 'idle' },
          { id: 2, name: 'Jane', status: 'idle' }
        ]
      };

      const actual = activeSessionReducer(
        stateWithParticipants,
        updateParticipant({ id: 1, status: 'active' })
      );

      expect(actual.participants[0].status).toBe('active');
      expect(actual.participants[0].name).toBe('John'); // Other fields preserved
    });
  });

  describe('track actions', () => {
    it('should handle setUserTracks', () => {
      const tracks = [
        { id: 1, name: 'Track 1' },
        { id: 2, name: 'Track 2' }
      ];

      const actual = activeSessionReducer(initialState, setUserTracks(tracks));
      expect(actual.userTracks).toEqual(tracks);
    });

    it('should handle addUserTrack', () => {
      const track = { id: 1, name: 'New Track' };
      const actual = activeSessionReducer(initialState, addUserTrack(track));

      expect(actual.userTracks).toHaveLength(1);
      expect(actual.userTracks[0]).toEqual(track);
    });

    it('should handle removeUserTrack', () => {
      const stateWithTracks = {
        ...initialState,
        userTracks: [
          { id: 1, name: 'Track 1' },
          { id: 2, name: 'Track 2' }
        ]
      };

      const actual = activeSessionReducer(stateWithTracks, removeUserTrack(1));
      expect(actual.userTracks).toHaveLength(1);
      expect(actual.userTracks[0].id).toBe(2);
    });
  });

  describe('jam track actions', () => {
    it('should handle setSelectedJamTrack', () => {
      const jamTrack = { id: 1, name: 'Blues Jam', tempo: 120 };
      const actual = activeSessionReducer(initialState, setSelectedJamTrack(jamTrack));

      expect(actual.selectedJamTrack).toEqual(jamTrack);
    });
  });

  describe('recording actions', () => {
    it('should handle startRecording', () => {
      const recordingId = 'rec-123';
      const actual = activeSessionReducer(initialState, startRecording(recordingId));

      expect(actual.recordingState.isRecording).toBe(true);
      expect(actual.recordingState.recordingId).toBe(recordingId);
    });

    it('should handle stopRecording', () => {
      const stateWithRecording = {
        ...initialState,
        recordingState: {
          isRecording: true,
          recordingId: 'rec-123',
          recordedTracks: []
        }
      };

      const actual = activeSessionReducer(stateWithRecording, stopRecording());
      expect(actual.recordingState.isRecording).toBe(false);
      expect(actual.recordingState.recordingId).toBe('rec-123'); // ID persists
    });
  });

  describe('async thunks', () => {
    describe('fetchActiveSession', () => {
      it('should handle fetchActiveSession.pending', () => {
        const action = { type: fetchActiveSession.pending.type };
        const state = activeSessionReducer(initialState, action);

        expect(state.loading).toBe(true);
        expect(state.error).toBe(null);
      });

      it('should handle fetchActiveSession.fulfilled', () => {
        const sessionData = {
          id: 'session-123',
          name: 'Test Session',
          participants: []
        };

        const action = {
          type: fetchActiveSession.fulfilled.type,
          payload: sessionData
        };

        const state = activeSessionReducer(initialState, action);

        expect(state.loading).toBe(false);
        expect(state.sessionData).toEqual(sessionData);
        expect(state.sessionId).toBe('session-123');
      });

      it('should handle fetchActiveSession.rejected', () => {
        const errorMessage = 'Failed to fetch session';
        const action = {
          type: fetchActiveSession.rejected.type,
          payload: errorMessage
        };

        const state = activeSessionReducer(initialState, action);

        expect(state.loading).toBe(false);
        expect(state.error).toBe(errorMessage);
      });
    });

    describe('joinActiveSession', () => {
      it('should handle joinActiveSession.pending', () => {
        const action = { type: joinActiveSession.pending.type };
        const state = activeSessionReducer(initialState, action);

        expect(state.joinStatus).toBe('joining');
        expect(state.error).toBe(null);
      });

      it('should handle joinActiveSession.fulfilled', () => {
        const sessionData = {
          id: 'session-123',
          name: 'Test Session',
          participants: [{ id: 1, name: 'User 1' }]
        };

        const action = {
          type: joinActiveSession.fulfilled.type,
          payload: sessionData
        };

        const state = activeSessionReducer(initialState, action);

        expect(state.joinStatus).toBe('joined');
        expect(state.hasJoined).toBe(true);
        expect(state.sessionData).toEqual(sessionData);
      });

      it('should handle joinActiveSession.rejected', () => {
        const errorMessage = 'Failed to join session';
        const action = {
          type: joinActiveSession.rejected.type,
          payload: errorMessage
        };

        const state = activeSessionReducer(initialState, action);

        expect(state.joinStatus).toBe('idle');
        expect(state.error).toBe(errorMessage);
      });
    });

    describe('leaveActiveSession', () => {
      it('should handle leaveActiveSession.pending', () => {
        const stateWithJoined = {
          ...initialState,
          joinStatus: 'joined',
          hasJoined: true
        };

        const action = { type: leaveActiveSession.pending.type };
        const state = activeSessionReducer(stateWithJoined, action);

        expect(state.joinStatus).toBe('leaving');
      });

      it('should handle leaveActiveSession.fulfilled', () => {
        const stateWithData = {
          ...initialState,
          sessionId: 'session-123',
          hasJoined: true,
          joinStatus: 'joined',
          userTracks: [{ id: 1, name: 'Track 1' }]
        };

        const action = { type: leaveActiveSession.fulfilled.type };
        const state = activeSessionReducer(stateWithData, action);

        // Should reset to initial state
        expect(state).toEqual(initialState);
      });

      it('should handle leaveActiveSession.rejected', () => {
        const errorMessage = 'Failed to leave session';
        const action = {
          type: leaveActiveSession.rejected.type,
          payload: errorMessage
        };

        const state = activeSessionReducer(initialState, action);

        expect(state.error).toBe(errorMessage);
      });
    });
  });

  describe('selectors', () => {
    const mockState = {
      activeSession: {
        sessionId: 'session-123',
        sessionData: { id: 'session-123', name: 'Test Session' },
        joinStatus: 'joined',
        guardsPassed: true,
        hasJoined: true,
        participants: [{ id: 1, name: 'User 1' }],
        userTracks: [
          { id: 1, name: 'Track 1' },
          { id: 2, name: 'Track 2' }
        ],
        connectionStatus: 'connected',
        showConnectionAlert: false,
        recordingState: {
          isRecording: true,
          recordingId: 'rec-123',
          recordedTracks: []
        },
        loading: false,
        error: null
      }
    };

    it('selectActiveSession should return session data', () => {
      expect(selectActiveSession(mockState)).toEqual(mockState.activeSession.sessionData);
    });

    it('selectSessionId should return session ID', () => {
      expect(selectSessionId(mockState)).toBe('session-123');
    });

    it('selectJoinStatus should return join status', () => {
      expect(selectJoinStatus(mockState)).toBe('joined');
    });

    it('selectHasJoined should return has joined state', () => {
      expect(selectHasJoined(mockState)).toBe(true);
    });

    it('selectGuardsPassed should return guards passed state', () => {
      expect(selectGuardsPassed(mockState)).toBe(true);
    });

    it('selectUserTracks should return user tracks', () => {
      expect(selectUserTracks(mockState)).toEqual(mockState.activeSession.userTracks);
    });

    it('selectConnectionStatus should return connection status', () => {
      expect(selectConnectionStatus(mockState)).toBe('connected');
    });

    it('selectIsRecording should return recording state', () => {
      expect(selectIsRecording(mockState)).toBe(true);
    });
  });

  describe('Phase 3: jam track actions', () => {
    it('should handle setSelectedJamTrack', () => {
      const jamTrack = { id: 1, name: 'Test Jam Track', duration: 180 };
      const actual = activeSessionReducer(initialState, setSelectedJamTrack(jamTrack));

      expect(actual.selectedJamTrack).toEqual(jamTrack);
    });

    it('should handle setSelectedJamTrack to null', () => {
      const stateWithJamTrack = {
        ...initialState,
        selectedJamTrack: { id: 1, name: 'Test Jam Track' }
      };

      const actual = activeSessionReducer(stateWithJamTrack, setSelectedJamTrack(null));
      expect(actual.selectedJamTrack).toBeNull();
    });

    it('should handle setJamTrackStems', () => {
      const stems = [
        { id: 1, name: 'Drums', type: 'audio' },
        { id: 2, name: 'Bass', type: 'audio' },
        { id: 3, name: 'Guitar', type: 'audio' }
      ];

      const actual = activeSessionReducer(initialState, setJamTrackStems(stems));
      expect(actual.jamTrackStems).toEqual(stems);
      expect(actual.jamTrackStems).toHaveLength(3);
    });

    it('should handle clearing jam track stems', () => {
      const stateWithStems = {
        ...initialState,
        jamTrackStems: [
          { id: 1, name: 'Drums' },
          { id: 2, name: 'Bass' }
        ]
      };

      const actual = activeSessionReducer(stateWithStems, setJamTrackStems([]));
      expect(actual.jamTrackStems).toEqual([]);
    });
  });

  describe('Phase 3: backing track actions', () => {
    it('should handle setBackingTrackData', () => {
      const backingTrackData = {
        backingTrack: { id: 1, name: 'Test Track.mp3', url: '/tracks/test.mp3' },
        jamClient: { id: 'client-123' },
        session: { id: 'session-456' },
        currentUser: { id: 'user-789' }
      };

      const actual = activeSessionReducer(initialState, setBackingTrackData(backingTrackData));
      expect(actual.backingTrackData).toEqual(backingTrackData);
      expect(actual.backingTrackData.backingTrack.name).toBe('Test Track.mp3');
    });

    it('should handle clearBackingTrackData', () => {
      const stateWithBackingTrack = {
        ...initialState,
        backingTrackData: {
          backingTrack: { id: 1, name: 'Test Track.mp3' },
          jamClient: { id: 'client-123' }
        }
      };

      const actual = activeSessionReducer(stateWithBackingTrack, clearBackingTrackData());
      expect(actual.backingTrackData).toBeNull();
    });

    it('should handle setting backing track data multiple times', () => {
      const firstTrack = {
        backingTrack: { id: 1, name: 'First Track.mp3' },
        jamClient: { id: 'client-123' }
      };
      const secondTrack = {
        backingTrack: { id: 2, name: 'Second Track.mp3' },
        jamClient: { id: 'client-456' }
      };

      let state = activeSessionReducer(initialState, setBackingTrackData(firstTrack));
      expect(state.backingTrackData.backingTrack.id).toBe(1);

      state = activeSessionReducer(state, setBackingTrackData(secondTrack));
      expect(state.backingTrackData.backingTrack.id).toBe(2);
      expect(state.backingTrackData.backingTrack.name).toBe('Second Track.mp3');
    });
  });

  describe('Phase 3: session metadata actions', () => {
    it('should handle setSessionRules', () => {
      const sessionRules = {
        maxParticipants: 8,
        allowRecording: true,
        requireInvite: false
      };

      const actual = activeSessionReducer(initialState, setSessionRules(sessionRules));
      expect(actual.sessionRules).toEqual(sessionRules);
    });

    it('should handle setSubscriptionRules', () => {
      const subscriptionRules = {
        tier: 'premium',
        maxDuration: 7200,
        features: ['recording', 'video', 'jamTracks']
      };

      const actual = activeSessionReducer(initialState, setSubscriptionRules(subscriptionRules));
      expect(actual.subscriptionRules).toEqual(subscriptionRules);
    });

    it('should handle clearing session rules', () => {
      const stateWithRules = {
        ...initialState,
        sessionRules: { maxParticipants: 8 },
        subscriptionRules: { tier: 'premium' }
      };

      const actual = activeSessionReducer(stateWithRules, clearSession());
      expect(actual.sessionRules).toBeNull();
      expect(actual.subscriptionRules).toBeNull();
    });
  });

  describe('Phase 3: selectors', () => {
    const mockState = {
      activeSession: {
        ...initialState,
        sessionRules: { maxParticipants: 8 },
        subscriptionRules: { tier: 'premium' },
        selectedJamTrack: { id: 1, name: 'Test Jam Track' },
        jamTrackStems: [{ id: 1, name: 'Drums' }],
        backingTrackData: { backingTrack: { id: 1, name: 'Track.mp3' } }
      }
    };

    it('should select session rules', () => {
      expect(selectSessionRules(mockState)).toEqual({ maxParticipants: 8 });
    });

    it('should select subscription rules', () => {
      expect(selectSubscriptionRules(mockState)).toEqual({ tier: 'premium' });
    });

    it('should select selected jam track', () => {
      expect(selectSelectedJamTrack(mockState)).toEqual({ id: 1, name: 'Test Jam Track' });
    });

    it('should select jam track stems', () => {
      expect(selectJamTrackStems(mockState)).toEqual([{ id: 1, name: 'Drums' }]);
    });

    it('should select backing track data', () => {
      expect(selectBackingTrackData(mockState)).toEqual({ backingTrack: { id: 1, name: 'Track.mp3' } });
    });
  });

  describe('edge cases', () => {
    it('should handle multiple guards pass toggles', () => {
      let state = activeSessionReducer(initialState, setGuardsPassed(true));
      expect(state.guardsPassed).toBe(true);

      state = activeSessionReducer(state, setGuardsPassed(false));
      expect(state.guardsPassed).toBe(false);
    });

    it('should handle connection status transitions', () => {
      let state = activeSessionReducer(initialState, setConnectionStatus('connected'));
      expect(state.connectionStatus).toBe('connected');
      expect(state.showConnectionAlert).toBe(false);

      state = activeSessionReducer(state, setConnectionStatus('reconnecting'));
      expect(state.connectionStatus).toBe('reconnecting');

      state = activeSessionReducer(state, setConnectionStatus('disconnected'));
      expect(state.connectionStatus).toBe('disconnected');
      expect(state.showConnectionAlert).toBe(true);
    });

    it('should handle clearing session with active recording', () => {
      const stateWithRecording = {
        ...initialState,
        sessionId: 'session-123',
        recordingState: {
          isRecording: true,
          recordingId: 'rec-123',
          recordedTracks: [{ id: 1 }]
        }
      };

      const actual = activeSessionReducer(stateWithRecording, clearSession());

      expect(actual).toEqual(initialState);
      expect(actual.recordingState.isRecording).toBe(false);
    });
  });

  describe('Phase 4: selectInSession and updateSessionData', () => {
    it('should select inSession as true when sessionId exists', () => {
      const state = { activeSession: { sessionId: '123' } };
      expect(selectInSession(state)).toBe(true);
    });

    it('should select inSession as false when sessionId is null', () => {
      const state = { activeSession: { sessionId: null } };
      expect(selectInSession(state)).toBe(false);
    });

    it('should handle updateSessionData action', () => {
      const stateWithSession = {
        ...initialState,
        sessionData: { id: '123', name: 'Test', participants: [] }
      };

      const action = updateSessionData({ participants: [{ id: 1, name: 'User 1' }] });
      const result = activeSessionReducer(stateWithSession, action);

      expect(result.sessionData.participants).toEqual([{ id: 1, name: 'User 1' }]);
      expect(result.sessionData.id).toBe('123'); // Other fields preserved
      expect(result.sessionData.name).toBe('Test'); // Other fields preserved
    });

    it('should not update sessionData if null', () => {
      const stateWithNullSession = { ...initialState, sessionData: null };
      const action = updateSessionData({ name: 'Test' });
      const result = activeSessionReducer(stateWithNullSession, action);

      expect(result.sessionData).toBeNull();
    });

    it('should handle partial updates to sessionData', () => {
      const stateWithSession = {
        ...initialState,
        sessionData: {
          id: '123',
          name: 'Original Name',
          participants: [{ id: 1 }],
          metronome_active: false
        }
      };

      const action = updateSessionData({ metronome_active: true });
      const result = activeSessionReducer(stateWithSession, action);

      expect(result.sessionData.metronome_active).toBe(true);
      expect(result.sessionData.id).toBe('123');
      expect(result.sessionData.name).toBe('Original Name');
      expect(result.sessionData.participants).toEqual([{ id: 1 }]);
    });
  });
});
