import mixersReducer, {
  setMasterMixers,
  setPersonalMixers,
  organizeMixers,
  setRecordingTrackMixers,
  setBackingTrackMixers,
  setJamTrackMixers,
  setMetronomeTrackMixers,
  setAdhocTrackMixers,
  updateMixer,
  setMetronome,
  setMetronomeSettings,
  updateMediaSummary,
  setMediaSummary,
  setChatMixer,
  setBroadcastMixer,
  setRecordingMixer,
  setSimulatedMusicCategoryMixers,
  setSimulatedChatCategoryMixers,
  addNoAudioUser,
  removeNoAudioUser,
  setNoAudioUsers,
  setClientsWithAudioOverride,
  setMissingMixerPeers,
  setCheckingMissingPeers,
  clearMixers,
  selectAllMixers,
  selectMasterMixers,
  selectPersonalMixers,
  selectMixersByResourceId,
  selectMixersByTrackId,
  selectChatMixer,
  selectBroadcastMixer,
  selectRecordingMixer,
  selectMetronome,
  selectMetronomeSettings,
  selectMediaSummary,
  selectMixersReady,
  selectBackingTrackMixers,
  selectJamTrackMixers,
  selectRecordingTrackMixers,
  selectMetronomeTrackMixers,
  selectAdhocTrackMixers,
  selectSimulatedMusicCategoryMixers,
  selectSimulatedChatCategoryMixers,
  selectNoAudioUsers,
  selectClientsWithAudioOverride,
  selectMissingMixerPeers,
  selectCheckingMissingPeers,
  selectMixer,
  selectMixerPairByResourceId,
  selectMixerPairByTrackId
} from '../mixersSlice';

describe('mixersSlice', () => {
  const initialState = {
    chatMixer: null,
    broadcastMixer: null,
    recordingMixer: null,
    recordingTrackMixers: [],
    backingTrackMixers: [],
    jamTrackMixers: [],
    metronomeTrackMixers: [],
    adhocTrackMixers: [],
    masterMixers: [],
    personalMixers: [],
    allMixers: {},
    mixersByResourceId: {},
    mixersByTrackId: {},
    simulatedMusicCategoryMixers: {
      PERSONAL: null,
      MASTER: null
    },
    simulatedChatCategoryMixers: {
      PERSONAL: null,
      MASTER: null
    },
    metronome: null,
    metronomeSettings: {
      tempo: 120,
      sound: "Beep",
      cricket: false
    },
    mediaSummary: {
      mediaOpen: false,
      backingTrackOpen: false,
      jamTrackOpen: false,
      recordingOpen: false,
      metronomeOpen: false,
      isOpener: false,
      userNeedsMediaControls: false,
      jamTrack: null
    },
    noAudioUsers: {},
    clientsWithAudioOverride: {},
    missingMixerPeers: {},
    checkingMissingPeers: {},
    isReady: false,
    loading: false,
    error: null
  };

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

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

  describe('master and personal mixers', () => {
    it('should handle setMasterMixers', () => {
      const masterMixers = [
        { id: 1, rid: 'rid1', name: 'Master Mixer 1' },
        { id: 2, rid: 'rid2', name: 'Master Mixer 2' }
      ];

      const actual = mixersReducer(initialState, setMasterMixers(masterMixers));
      expect(actual.masterMixers).toEqual(masterMixers);
      expect(actual.masterMixers).toHaveLength(2);
    });

    it('should handle setPersonalMixers', () => {
      const personalMixers = [
        { id: 1, rid: 'rid1', name: 'Personal Mixer 1' },
        { id: 2, rid: 'rid2', name: 'Personal Mixer 2' }
      ];

      const actual = mixersReducer(initialState, setPersonalMixers(personalMixers));
      expect(actual.personalMixers).toEqual(personalMixers);
      expect(actual.personalMixers).toHaveLength(2);
    });
  });

  describe('organizeMixers', () => {
    it('should organize mixers and build lookup tables', () => {
      const stateWithMixers = {
        ...initialState,
        masterMixers: [
          { id: 1, rid: 'rid1', name: 'Master 1' },
          { id: 2, rid: 'rid2', name: 'Master 2' }
        ],
        personalMixers: [
          { id: 1, rid: 'rid1', name: 'Personal 1' },
          { id: 2, rid: 'rid2', name: 'Personal 2' }
        ]
      };

      const actual = mixersReducer(stateWithMixers, organizeMixers());

      // Check allMixers
      expect(actual.allMixers['M1']).toEqual({ id: 1, rid: 'rid1', name: 'Master 1' });
      expect(actual.allMixers['M2']).toEqual({ id: 2, rid: 'rid2', name: 'Master 2' });
      expect(actual.allMixers['P1']).toEqual({ id: 1, rid: 'rid1', name: 'Personal 1' });
      expect(actual.allMixers['P2']).toEqual({ id: 2, rid: 'rid2', name: 'Personal 2' });

      // Check mixersByResourceId
      expect(actual.mixersByResourceId['rid1'].master).toEqual({ id: 1, rid: 'rid1', name: 'Master 1' });
      expect(actual.mixersByResourceId['rid1'].personal).toEqual({ id: 1, rid: 'rid1', name: 'Personal 1' });
      expect(actual.mixersByResourceId['rid2'].master).toEqual({ id: 2, rid: 'rid2', name: 'Master 2' });
      expect(actual.mixersByResourceId['rid2'].personal).toEqual({ id: 2, rid: 'rid2', name: 'Personal 2' });

      // Check mixersByTrackId
      expect(actual.mixersByTrackId[1].master).toEqual({ id: 1, rid: 'rid1', name: 'Master 1' });
      expect(actual.mixersByTrackId[1].personal).toEqual({ id: 1, rid: 'rid1', name: 'Personal 1' });

      // Check isReady flag
      expect(actual.isReady).toBe(true);
    });

    it('should handle personal mixer without matching master', () => {
      const stateWithMixers = {
        ...initialState,
        masterMixers: [
          { id: 1, rid: 'rid1', name: 'Master 1' }
        ],
        personalMixers: [
          { id: 1, rid: 'rid1', name: 'Personal 1' },
          { id: 2, rid: 'rid2', name: 'Personal 2 (no master)' }
        ]
      };

      const actual = mixersReducer(stateWithMixers, organizeMixers());

      // Personal mixer without master should still be added
      expect(actual.allMixers['P2']).toEqual({ id: 2, rid: 'rid2', name: 'Personal 2 (no master)' });
      expect(actual.mixersByResourceId['rid2'].personal).toEqual({ id: 2, rid: 'rid2', name: 'Personal 2 (no master)' });
      expect(actual.mixersByResourceId['rid2'].master).toBeUndefined();
    });
  });

  describe('mixer type arrays', () => {
    it('should handle setRecordingTrackMixers', () => {
      const mixers = [{ id: 1, name: 'Recording Track 1' }];
      const actual = mixersReducer(initialState, setRecordingTrackMixers(mixers));
      expect(actual.recordingTrackMixers).toEqual(mixers);
    });

    it('should handle setBackingTrackMixers', () => {
      const mixers = [{ id: 2, name: 'Backing Track 1' }];
      const actual = mixersReducer(initialState, setBackingTrackMixers(mixers));
      expect(actual.backingTrackMixers).toEqual(mixers);
    });

    it('should handle setJamTrackMixers', () => {
      const mixers = [{ id: 3, name: 'Jam Track 1' }];
      const actual = mixersReducer(initialState, setJamTrackMixers(mixers));
      expect(actual.jamTrackMixers).toEqual(mixers);
    });

    it('should handle setMetronomeTrackMixers', () => {
      const mixers = [{ id: 4, name: 'Metronome Track 1' }];
      const actual = mixersReducer(initialState, setMetronomeTrackMixers(mixers));
      expect(actual.metronomeTrackMixers).toEqual(mixers);
    });

    it('should handle setAdhocTrackMixers', () => {
      const mixers = [{ id: 5, name: 'Adhoc Track 1' }];
      const actual = mixersReducer(initialState, setAdhocTrackMixers(mixers));
      expect(actual.adhocTrackMixers).toEqual(mixers);
    });
  });

  describe('updateMixer', () => {
    it('should update master mixer in allMixers and masterMixers arrays', () => {
      const stateWithMixers = {
        ...initialState,
        masterMixers: [
          { id: 1, rid: 'rid1', volume: 0.5 },
          { id: 2, rid: 'rid2', volume: 0.7 }
        ],
        allMixers: {
          'M1': { id: 1, rid: 'rid1', volume: 0.5 },
          'M2': { id: 2, rid: 'rid2', volume: 0.7 }
        }
      };

      const actual = mixersReducer(
        stateWithMixers,
        updateMixer({ mixerId: 1, mode: true, updates: { volume: 0.8 } })
      );

      expect(actual.allMixers['M1'].volume).toBe(0.8);
      expect(actual.masterMixers[0].volume).toBe(0.8);
    });

    it('should update personal mixer in allMixers and personalMixers arrays', () => {
      const stateWithMixers = {
        ...initialState,
        personalMixers: [
          { id: 1, rid: 'rid1', volume: 0.5 },
          { id: 2, rid: 'rid2', volume: 0.7 }
        ],
        allMixers: {
          'P1': { id: 1, rid: 'rid1', volume: 0.5 },
          'P2': { id: 2, rid: 'rid2', volume: 0.7 }
        }
      };

      const actual = mixersReducer(
        stateWithMixers,
        updateMixer({ mixerId: 1, mode: false, updates: { volume: 0.9 } })
      );

      expect(actual.allMixers['P1'].volume).toBe(0.9);
      expect(actual.personalMixers[0].volume).toBe(0.9);
    });

    it('should not fail when updating non-existent mixer', () => {
      const actual = mixersReducer(
        initialState,
        updateMixer({ mixerId: 999, mode: true, updates: { volume: 0.5 } })
      );

      expect(actual).toEqual(initialState);
    });
  });

  describe('metronome', () => {
    it('should handle setMetronome with metronome object', () => {
      const metronome = { bpm: 120, sound: 'Beep', meter: 1, mode: 0 };
      const actual = mixersReducer(initialState, setMetronome(metronome));

      expect(actual.metronome).toEqual(metronome);
      expect(actual.mediaSummary.metronomeOpen).toBe(true);
    });

    it('should handle setMetronome with null (close metronome)', () => {
      const stateWithMetronome = {
        ...initialState,
        metronome: { bpm: 120 },
        mediaSummary: { ...initialState.mediaSummary, metronomeOpen: true }
      };

      const actual = mixersReducer(stateWithMetronome, setMetronome(null));

      expect(actual.metronome).toBeNull();
      expect(actual.mediaSummary.metronomeOpen).toBe(false);
    });

    it('should handle setMetronomeSettings', () => {
      const actual = mixersReducer(
        initialState,
        setMetronomeSettings({ tempo: 140, sound: 'Click', cricket: true })
      );

      expect(actual.metronomeSettings.tempo).toBe(140);
      expect(actual.metronomeSettings.sound).toBe('Click');
      expect(actual.metronomeSettings.cricket).toBe(true);
    });

    it('should handle partial metronome settings update', () => {
      const actual = mixersReducer(
        initialState,
        setMetronomeSettings({ tempo: 160 })
      );

      expect(actual.metronomeSettings.tempo).toBe(160);
      expect(actual.metronomeSettings.sound).toBe('Beep'); // Unchanged
      expect(actual.metronomeSettings.cricket).toBe(false); // Unchanged
    });
  });

  describe('media summary', () => {
    it('should handle updateMediaSummary (partial update)', () => {
      const actual = mixersReducer(
        initialState,
        updateMediaSummary({ backingTrackOpen: true, userNeedsMediaControls: true })
      );

      expect(actual.mediaSummary.backingTrackOpen).toBe(true);
      expect(actual.mediaSummary.userNeedsMediaControls).toBe(true);
      expect(actual.mediaSummary.jamTrackOpen).toBe(false); // Unchanged
    });

    it('should handle setMediaSummary (full replacement)', () => {
      const newMediaSummary = {
        mediaOpen: true,
        backingTrackOpen: true,
        jamTrackOpen: false,
        recordingOpen: false,
        metronomeOpen: false,
        isOpener: true,
        userNeedsMediaControls: true,
        jamTrack: { id: 123, name: 'Test Track' }
      };

      const actual = mixersReducer(initialState, setMediaSummary(newMediaSummary));
      expect(actual.mediaSummary).toEqual(newMediaSummary);
    });
  });

  describe('chat, broadcast, recording mixers', () => {
    it('should handle setChatMixer', () => {
      const chatMixer = { id: 1, name: 'Chat Mixer' };
      const actual = mixersReducer(initialState, setChatMixer(chatMixer));
      expect(actual.chatMixer).toEqual(chatMixer);
    });

    it('should handle setBroadcastMixer', () => {
      const broadcastMixer = { id: 2, name: 'Broadcast Mixer' };
      const actual = mixersReducer(initialState, setBroadcastMixer(broadcastMixer));
      expect(actual.broadcastMixer).toEqual(broadcastMixer);
    });

    it('should handle setRecordingMixer', () => {
      const recordingMixer = { id: 3, name: 'Recording Mixer' };
      const actual = mixersReducer(initialState, setRecordingMixer(recordingMixer));
      expect(actual.recordingMixer).toEqual(recordingMixer);
    });
  });

  describe('simulated category mixers', () => {
    it('should handle setSimulatedMusicCategoryMixers', () => {
      const mixers = { PERSONAL: { volume: 0.8 }, MASTER: { volume: 0.9 } };
      const actual = mixersReducer(initialState, setSimulatedMusicCategoryMixers(mixers));
      expect(actual.simulatedMusicCategoryMixers).toEqual(mixers);
    });

    it('should handle setSimulatedChatCategoryMixers', () => {
      const mixers = { PERSONAL: { volume: 0.7 }, MASTER: { volume: 0.8 } };
      const actual = mixersReducer(initialState, setSimulatedChatCategoryMixers(mixers));
      expect(actual.simulatedChatCategoryMixers).toEqual(mixers);
    });
  });

  describe('peer management', () => {
    it('should handle addNoAudioUser', () => {
      const actual = mixersReducer(initialState, addNoAudioUser('user123'));
      expect(actual.noAudioUsers['user123']).toBe(true);
    });

    it('should handle removeNoAudioUser', () => {
      const stateWithNoAudioUser = {
        ...initialState,
        noAudioUsers: { 'user123': true, 'user456': true }
      };

      const actual = mixersReducer(stateWithNoAudioUser, removeNoAudioUser('user123'));
      expect(actual.noAudioUsers['user123']).toBeUndefined();
      expect(actual.noAudioUsers['user456']).toBe(true);
    });

    it('should handle setNoAudioUsers', () => {
      const noAudioUsers = { 'user1': true, 'user2': true };
      const actual = mixersReducer(initialState, setNoAudioUsers(noAudioUsers));
      expect(actual.noAudioUsers).toEqual(noAudioUsers);
    });

    it('should handle setClientsWithAudioOverride', () => {
      const clients = { 'client1': true, 'client2': false };
      const actual = mixersReducer(initialState, setClientsWithAudioOverride(clients));
      expect(actual.clientsWithAudioOverride).toEqual(clients);
    });

    it('should handle setMissingMixerPeers', () => {
      const peers = { 'peer1': true };
      const actual = mixersReducer(initialState, setMissingMixerPeers(peers));
      expect(actual.missingMixerPeers).toEqual(peers);
    });

    it('should handle setCheckingMissingPeers', () => {
      const checking = { 'peer1': true };
      const actual = mixersReducer(initialState, setCheckingMissingPeers(checking));
      expect(actual.checkingMissingPeers).toEqual(checking);
    });
  });

  describe('clearMixers', () => {
    it('should reset to initial state', () => {
      const stateWithData = {
        ...initialState,
        chatMixer: { id: 1 },
        masterMixers: [{ id: 1 }],
        personalMixers: [{ id: 2 }],
        allMixers: { 'M1': { id: 1 } },
        isReady: true,
        metronome: { bpm: 120 }
      };

      const actual = mixersReducer(stateWithData, clearMixers());
      expect(actual).toEqual(initialState);
    });
  });

  describe('selectors', () => {
    const mockState = {
      mixers: {
        ...initialState,
        chatMixer: { id: 1, name: 'Chat' },
        broadcastMixer: { id: 2, name: 'Broadcast' },
        masterMixers: [{ id: 1 }],
        personalMixers: [{ id: 2 }],
        allMixers: { 'M1': { id: 1 }, 'P2': { id: 2 } },
        mixersByResourceId: { 'rid1': { master: { id: 1 }, personal: { id: 2 } } },
        mixersByTrackId: { '1': { master: { id: 1 }, personal: { id: 2 } } },
        metronome: { bpm: 120 },
        metronomeSettings: { tempo: 140, sound: 'Click', cricket: false },
        mediaSummary: { backingTrackOpen: true },
        isReady: true,
        backingTrackMixers: [{ id: 3 }],
        noAudioUsers: { 'user1': true }
      }
    };

    it('should select all mixers', () => {
      expect(selectAllMixers(mockState)).toEqual({ 'M1': { id: 1 }, 'P2': { id: 2 } });
    });

    it('should select master mixers', () => {
      expect(selectMasterMixers(mockState)).toEqual([{ id: 1 }]);
    });

    it('should select personal mixers', () => {
      expect(selectPersonalMixers(mockState)).toEqual([{ id: 2 }]);
    });

    it('should select mixers by resource ID', () => {
      expect(selectMixersByResourceId(mockState)).toEqual({ 'rid1': { master: { id: 1 }, personal: { id: 2 } } });
    });

    it('should select mixers by track ID', () => {
      expect(selectMixersByTrackId(mockState)).toEqual({ '1': { master: { id: 1 }, personal: { id: 2 } } });
    });

    it('should select chat mixer', () => {
      expect(selectChatMixer(mockState)).toEqual({ id: 1, name: 'Chat' });
    });

    it('should select broadcast mixer', () => {
      expect(selectBroadcastMixer(mockState)).toEqual({ id: 2, name: 'Broadcast' });
    });

    it('should select metronome', () => {
      expect(selectMetronome(mockState)).toEqual({ bpm: 120 });
    });

    it('should select metronome settings', () => {
      expect(selectMetronomeSettings(mockState)).toEqual({ tempo: 140, sound: 'Click', cricket: false });
    });

    it('should select media summary', () => {
      expect(selectMediaSummary(mockState)).toEqual({ backingTrackOpen: true });
    });

    it('should select mixers ready flag', () => {
      expect(selectMixersReady(mockState)).toBe(true);
    });

    it('should select backing track mixers', () => {
      expect(selectBackingTrackMixers(mockState)).toEqual([{ id: 3 }]);
    });

    it('should select no audio users', () => {
      expect(selectNoAudioUsers(mockState)).toEqual({ 'user1': true });
    });

    it('should select specific mixer by ID and mode', () => {
      expect(selectMixer(1, true)(mockState)).toEqual({ id: 1 });
      expect(selectMixer(2, false)(mockState)).toEqual({ id: 2 });
    });

    it('should select mixer pair by resource ID', () => {
      expect(selectMixerPairByResourceId('rid1')(mockState)).toEqual({ master: { id: 1 }, personal: { id: 2 } });
    });

    it('should select mixer pair by track ID', () => {
      expect(selectMixerPairByTrackId('1')(mockState)).toEqual({ master: { id: 1 }, personal: { id: 2 } });
    });
  });
});
