import mediaReducer, {
  openBackingTrack,
  loadJamTrack,
  closeMedia,
  setBackingTracks,
  setJamTracks,
  setRecordedTracks,
  updateJamTrackState,
  clearJamTrackState,
  clearAllMedia,
  selectBackingTracks,
  selectJamTracks,
  selectRecordedTracks,
  selectJamTrackState,
  selectDownloadingJamTrack,
  selectMediaLoading,
  selectMediaError
} from '../mediaSlice';

describe('mediaSlice', () => {
  const initialState = {
    backingTracks: [],
    jamTracks: [],
    recordedTracks: [],
    jamTrackState: {},
    downloadingJamTrack: false,
    loading: {
      backingTrack: false,
      jamTrack: false,
      closing: false
    },
    error: null
  };

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

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

  describe('synchronous actions', () => {
    it('should handle setBackingTracks', () => {
      const tracks = [
        { id: 1, name: 'Track 1', isOpener: true },
        { id: 2, name: 'Track 2', isOpener: false }
      ];

      const actual = mediaReducer(initialState, setBackingTracks(tracks));
      expect(actual.backingTracks).toEqual(tracks);
      expect(actual.backingTracks).toHaveLength(2);
    });

    it('should handle setJamTracks', () => {
      const tracks = [
        { id: 1, name: 'Jam Track 1', part: 'drums' },
        { id: 2, name: 'Jam Track 2', part: 'bass' }
      ];

      const actual = mediaReducer(initialState, setJamTracks(tracks));
      expect(actual.jamTracks).toEqual(tracks);
    });

    it('should handle setRecordedTracks', () => {
      const tracks = [
        { id: 1, recordingName: 'Recording 1' }
      ];

      const actual = mediaReducer(initialState, setRecordedTracks(tracks));
      expect(actual.recordedTracks).toEqual(tracks);
    });

    it('should handle updateJamTrackState', () => {
      const actual = mediaReducer(
        initialState,
        updateJamTrackState({ playing: true, position: 1.5 })
      );

      expect(actual.jamTrackState.playing).toBe(true);
      expect(actual.jamTrackState.position).toBe(1.5);
    });

    it('should merge jam track state updates', () => {
      const stateWithJamTrack = {
        ...initialState,
        jamTrackState: { playing: true, position: 1.5 }
      };

      const actual = mediaReducer(
        stateWithJamTrack,
        updateJamTrackState({ position: 2.0, volume: 0.8 })
      );

      expect(actual.jamTrackState.playing).toBe(true); // Preserved
      expect(actual.jamTrackState.position).toBe(2.0); // Updated
      expect(actual.jamTrackState.volume).toBe(0.8); // Added
    });

    it('should handle clearJamTrackState', () => {
      const stateWithJamTrack = {
        ...initialState,
        jamTrackState: { playing: true, position: 1.5 }
      };

      const actual = mediaReducer(stateWithJamTrack, clearJamTrackState());
      expect(actual.jamTrackState).toEqual({});
    });

    it('should handle clearAllMedia', () => {
      const stateWithMedia = {
        ...initialState,
        backingTracks: [{ id: 1 }],
        jamTracks: [{ id: 2 }],
        recordedTracks: [{ id: 3 }],
        jamTrackState: { playing: true },
        downloadingJamTrack: true,
        error: 'Some error'
      };

      const actual = mediaReducer(stateWithMedia, clearAllMedia());
      expect(actual.backingTracks).toEqual([]);
      expect(actual.jamTracks).toEqual([]);
      expect(actual.recordedTracks).toEqual([]);
      expect(actual.jamTrackState).toEqual({});
      expect(actual.downloadingJamTrack).toBe(false);
      expect(actual.error).toBeNull();
    });
  });

  describe('async thunks: openBackingTrack', () => {
    it('should handle openBackingTrack.pending', () => {
      const action = { type: openBackingTrack.pending.type };
      const actual = mediaReducer(initialState, action);

      expect(actual.loading.backingTrack).toBe(true);
      expect(actual.error).toBeNull();
    });

    it('should handle openBackingTrack.fulfilled', () => {
      const stateWithLoading = {
        ...initialState,
        loading: { ...initialState.loading, backingTrack: true }
      };

      const action = {
        type: openBackingTrack.fulfilled.type,
        payload: { file: 'track.mp3' }
      };

      const actual = mediaReducer(stateWithLoading, action);
      expect(actual.loading.backingTrack).toBe(false);
    });

    it('should handle openBackingTrack.rejected', () => {
      const stateWithLoading = {
        ...initialState,
        loading: { ...initialState.loading, backingTrack: true }
      };

      const action = {
        type: openBackingTrack.rejected.type,
        payload: 'Failed to open backing track'
      };

      const actual = mediaReducer(stateWithLoading, action);
      expect(actual.loading.backingTrack).toBe(false);
      expect(actual.error).toBe('Failed to open backing track');
    });

    it('should call jamClient.SessionOpenBackingTrackFile', async () => {
      const mockJamClient = {
        SessionOpenBackingTrackFile: jest.fn().mockResolvedValue()
      };

      const dispatch = jest.fn();
      const thunk = openBackingTrack({
        file: 'test.mp3',
        jamClient: mockJamClient
      });

      await thunk(dispatch, () => ({}), undefined);

      expect(mockJamClient.SessionOpenBackingTrackFile).toHaveBeenCalledWith('test.mp3', false);
    });
  });

  describe('async thunks: loadJamTrack', () => {
    it('should handle loadJamTrack.pending', () => {
      const action = { type: loadJamTrack.pending.type };
      const actual = mediaReducer(initialState, action);

      expect(actual.loading.jamTrack).toBe(true);
      expect(actual.downloadingJamTrack).toBe(true);
      expect(actual.error).toBeNull();
    });

    it('should handle loadJamTrack.fulfilled', () => {
      const stateWithLoading = {
        ...initialState,
        loading: { ...initialState.loading, jamTrack: true },
        downloadingJamTrack: true
      };

      const action = {
        type: loadJamTrack.fulfilled.type,
        payload: { jamTrack: { id: 123 }, result: true }
      };

      const actual = mediaReducer(stateWithLoading, action);
      expect(actual.loading.jamTrack).toBe(false);
      expect(actual.downloadingJamTrack).toBe(false);
    });

    it('should handle loadJamTrack.rejected', () => {
      const stateWithLoading = {
        ...initialState,
        loading: { ...initialState.loading, jamTrack: true },
        downloadingJamTrack: true
      };

      const action = {
        type: loadJamTrack.rejected.type,
        payload: 'Unable to open JamTrack'
      };

      const actual = mediaReducer(stateWithLoading, action);
      expect(actual.loading.jamTrack).toBe(false);
      expect(actual.downloadingJamTrack).toBe(false);
      expect(actual.error).toBe('Unable to open JamTrack');
    });

    it('should call jamClient.JamTrackPlay without JMep', async () => {
      const mockJamClient = {
        JamTrackPlay: jest.fn().mockResolvedValue(true)
      };

      const dispatch = jest.fn();
      const thunk = loadJamTrack({
        jamTrack: { id: 123, name: 'Test Track' },
        jamClient: mockJamClient
      });

      await thunk(dispatch, () => ({}), undefined);

      expect(mockJamClient.JamTrackPlay).toHaveBeenCalledWith(123);
    });

    it('should call jamClient.JamTrackLoadJmep when JMep data available', async () => {
      const mockJamClient = {
        GetSampleRate: jest.fn().mockResolvedValue(48),
        JamTrackLoadJmep: jest.fn().mockResolvedValue(),
        JamTrackPlay: jest.fn().mockResolvedValue(true)
      };

      const dispatch = jest.fn();
      const thunk = loadJamTrack({
        jamTrack: { id: 123, name: 'Test Track', jmep: 'base64data...' },
        jamClient: mockJamClient
      });

      await thunk(dispatch, () => ({}), undefined);

      expect(mockJamClient.GetSampleRate).toHaveBeenCalled();
      expect(mockJamClient.JamTrackLoadJmep).toHaveBeenCalledWith('123-48', 'base64data...');
      expect(mockJamClient.JamTrackPlay).toHaveBeenCalledWith(123);
    });

    it('should use 44 sample rate filename when sample rate is not 48', async () => {
      const mockJamClient = {
        GetSampleRate: jest.fn().mockResolvedValue(44.1),
        JamTrackLoadJmep: jest.fn().mockResolvedValue(),
        JamTrackPlay: jest.fn().mockResolvedValue(true)
      };

      const dispatch = jest.fn();
      const thunk = loadJamTrack({
        jamTrack: { id: 456, jmep: 'base64data...' },
        jamClient: mockJamClient
      });

      await thunk(dispatch, () => ({}), undefined);

      expect(mockJamClient.JamTrackLoadJmep).toHaveBeenCalledWith('456-44', 'base64data...');
    });

    it('should reject when JamTrackPlay returns false', async () => {
      const mockJamClient = {
        JamTrackPlay: jest.fn().mockResolvedValue(false)
      };

      const dispatch = jest.fn();
      const getState = jest.fn();
      const thunk = loadJamTrack({
        jamTrack: { id: 123 },
        jamClient: mockJamClient
      });

      const result = await thunk(dispatch, getState, undefined);

      expect(result.type).toBe('media/loadJamTrack/rejected');
      expect(result.payload).toBe('Unable to open JamTrack');
    });
  });

  describe('async thunks: closeMedia', () => {
    it('should handle closeMedia.pending', () => {
      const action = { type: closeMedia.pending.type };
      const actual = mediaReducer(initialState, action);

      expect(actual.loading.closing).toBe(true);
    });

    it('should handle closeMedia.fulfilled and clear all media', () => {
      const stateWithMedia = {
        ...initialState,
        backingTracks: [{ id: 1 }],
        jamTracks: [{ id: 2 }],
        recordedTracks: [{ id: 3 }],
        jamTrackState: { playing: true },
        loading: { ...initialState.loading, closing: true }
      };

      const action = { type: closeMedia.fulfilled.type };
      const actual = mediaReducer(stateWithMedia, action);

      expect(actual.loading.closing).toBe(false);
      expect(actual.backingTracks).toEqual([]);
      expect(actual.jamTracks).toEqual([]);
      expect(actual.recordedTracks).toEqual([]);
      expect(actual.jamTrackState).toEqual({});
    });

    it('should handle closeMedia.rejected', () => {
      const stateWithLoading = {
        ...initialState,
        loading: { ...initialState.loading, closing: true }
      };

      const action = {
        type: closeMedia.rejected.type,
        payload: 'Failed to close media'
      };

      const actual = mediaReducer(stateWithLoading, action);
      expect(actual.loading.closing).toBe(false);
      expect(actual.error).toBe('Failed to close media');
    });

    it('should call jamClient.SessionCloseMedia with force parameter', async () => {
      const mockJamClient = {
        SessionCloseMedia: jest.fn().mockResolvedValue()
      };

      const dispatch = jest.fn();
      const thunk = closeMedia({
        force: true,
        jamClient: mockJamClient
      });

      await thunk(dispatch, () => ({}), undefined);

      expect(mockJamClient.SessionCloseMedia).toHaveBeenCalledWith(true);
    });

    it('should default force to false', async () => {
      const mockJamClient = {
        SessionCloseMedia: jest.fn().mockResolvedValue()
      };

      const dispatch = jest.fn();
      const thunk = closeMedia({
        jamClient: mockJamClient
      });

      await thunk(dispatch, () => ({}), undefined);

      expect(mockJamClient.SessionCloseMedia).toHaveBeenCalledWith(false);
    });
  });

  describe('selectors', () => {
    const mockState = {
      media: {
        backingTracks: [{ id: 1, name: 'Backing 1' }],
        jamTracks: [{ id: 2, name: 'Jam 1' }],
        recordedTracks: [{ id: 3, name: 'Recording 1' }],
        jamTrackState: { playing: true, position: 2.5 },
        downloadingJamTrack: true,
        loading: {
          backingTrack: false,
          jamTrack: true,
          closing: false
        },
        error: 'Test error'
      }
    };

    it('should select backing tracks', () => {
      expect(selectBackingTracks(mockState)).toEqual([{ id: 1, name: 'Backing 1' }]);
    });

    it('should select jam tracks', () => {
      expect(selectJamTracks(mockState)).toEqual([{ id: 2, name: 'Jam 1' }]);
    });

    it('should select recorded tracks', () => {
      expect(selectRecordedTracks(mockState)).toEqual([{ id: 3, name: 'Recording 1' }]);
    });

    it('should select jam track state', () => {
      expect(selectJamTrackState(mockState)).toEqual({ playing: true, position: 2.5 });
    });

    it('should select downloading jam track flag', () => {
      expect(selectDownloadingJamTrack(mockState)).toBe(true);
    });

    it('should select media loading states', () => {
      expect(selectMediaLoading(mockState)).toEqual({
        backingTrack: false,
        jamTrack: true,
        closing: false
      });
    });

    it('should select media error', () => {
      expect(selectMediaError(mockState)).toBe('Test error');
    });
  });
});
