import { buildTrackSyncPayload, syncTracksToServer } from '../trackSyncService';
import { putTrackSyncChange } from '../../helpers/rest';

// Mock the REST API
jest.mock('../../helpers/rest', () => ({
  putTrackSyncChange: jest.fn()
}));

// Mock Redux actions
const mockDispatch = jest.fn((action) => {
  if (typeof action === 'function') {
    return action(mockDispatch, mockGetState);
  }
  return action;
});

const mockGetState = jest.fn();

describe('trackSyncService', () => {
  // Mock clientId
  const mockClientId = 'test-client-uuid';

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

  describe('buildTrackSyncPayload', () => {
    test('builds correct payload from Redux state with user tracks', () => {
      const mockState = {
        activeSession: {
          userTracks: [
            {
              id: 'track-1',
              rid: 'resource-1',
              instrument_id: 50, // Electric Guitar (client ID)
              stereo: true
            }
          ],
          backingTracks: [],
          metronome: {
            isOpen: false
          }
        }
      };

      const payload = buildTrackSyncPayload(mockState, 'session-123', 'test-client-uuid');

      expect(payload).toEqual({
        client_id: 'test-client-uuid',
        tracks: [
          {
            client_track_id: 'track-1',
            client_resource_id: 'resource-1',
            instrument_id: 'electric guitar', // Mapped to server ID
            sound: 'stereo'
          }
        ],
        backing_tracks: [],
        metronome_open: false
      });
    });

    test('builds payload with mono sound when track is not stereo', () => {
      const mockState = {
        activeSession: {
          userTracks: [
            {
              id: 'track-1',
              rid: 'resource-1',
              instrument_id: 40, // Drums
              stereo: false
            }
          ],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      const payload = buildTrackSyncPayload(mockState, 'session-123', 'test-client-uuid');

      expect(payload.tracks[0].sound).toBe('mono');
    });

    test('builds payload with backing tracks', () => {
      const mockState = {
        activeSession: {
          userTracks: [],
          backingTracks: [
            {
              id: 'backing-1',
              rid: 'backing-resource-1',
              filename: 'backing-track.mp3'
            }
          ],
          metronome: { isOpen: false }
        }
      };

      const payload = buildTrackSyncPayload(mockState, 'session-123', 'test-client-uuid');

      expect(payload.backing_tracks).toEqual([
        {
          client_track_id: 'backing-1',
          client_resource_id: 'backing-resource-1',
          filename: 'backing-track.mp3'
        }
      ]);
    });

    test('builds payload with metronome open', () => {
      const mockState = {
        activeSession: {
          userTracks: [],
          backingTracks: [],
          metronome: {
            isOpen: true
          }
        }
      };

      const payload = buildTrackSyncPayload(mockState, 'session-123', 'test-client-uuid');

      expect(payload.metronome_open).toBe(true);
    });

    test('handles missing metronome state gracefully', () => {
      const mockState = {
        activeSession: {
          userTracks: [],
          backingTracks: [],
          metronome: null
        }
      };

      const payload = buildTrackSyncPayload(mockState, 'session-123', 'test-client-uuid');

      expect(payload.metronome_open).toBe(false);
    });

    test('handles empty tracks array', () => {
      const mockState = {
        activeSession: {
          userTracks: [],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      const payload = buildTrackSyncPayload(mockState, 'session-123', 'test-client-uuid');

      expect(payload.tracks).toEqual([]);
      expect(payload.backing_tracks).toEqual([]);
    });

    test('maps multiple instruments correctly', () => {
      const mockState = {
        activeSession: {
          userTracks: [
            { id: 't1', rid: 'r1', instrument_id: 10, stereo: true },  // Acoustic Guitar
            { id: 't2', rid: 'r2', instrument_id: 61, stereo: false }, // Piano (correct mapping)
            { id: 't3', rid: 'r3', instrument_id: 70, stereo: true }   // Voice (correct mapping)
          ],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      const payload = buildTrackSyncPayload(mockState, 'session-123', 'test-client-uuid');

      expect(payload.tracks).toHaveLength(3);
      expect(payload.tracks[0].instrument_id).toBe('acoustic guitar');
      expect(payload.tracks[1].instrument_id).toBe('piano');
      expect(payload.tracks[2].instrument_id).toBe('voice');
    });
  });

  describe('syncTracksToServer', () => {
    test('calls API with correct payload', async () => {
      const mockState = {
        activeSession: {
          sessionId: 'session-123',
          hasJoined: true,
          userTracks: [
            { id: 'track-1', rid: 'resource-1', instrument_id: 50, stereo: true }
          ],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      mockGetState.mockReturnValue(mockState);

      putTrackSyncChange.mockResolvedValue({
        tracks: [{ id: 'track-1' }],
        backing_tracks: []
      });

      const result = await syncTracksToServer('session-123', mockClientId)(mockDispatch, mockGetState);

      expect(putTrackSyncChange).toHaveBeenCalledWith({
        id: 'session-123',
        client_id: 'test-client-uuid',
        tracks: [
          {
            client_track_id: 'track-1',
            client_resource_id: 'resource-1',
            instrument_id: 'electric guitar',
            sound: 'stereo'
          }
        ],
        backing_tracks: [],
        metronome_open: false
      });

      expect(result.success).toBe(true);
    });

    test('skips sync when clientId is missing', async () => {
      const mockState = {
        activeSession: {
          sessionId: 'session-123',
          hasJoined: true,
          userTracks: [],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      mockGetState.mockReturnValue(mockState);

      const result = await syncTracksToServer('session-123', null)(mockDispatch, mockGetState);

      expect(putTrackSyncChange).not.toHaveBeenCalled();
      expect(result.skipped).toBe(true);
      expect(result.reason).toBe('no_client_id');
    });

    test('skips sync when sessionId is missing', async () => {
      const mockState = {
        activeSession: {
          hasJoined: true,
          userTracks: [],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      mockGetState.mockReturnValue(mockState);

      const result = await syncTracksToServer(null, mockClientId)(mockDispatch, mockGetState);

      expect(putTrackSyncChange).not.toHaveBeenCalled();
      expect(result.skipped).toBe(true);
      expect(result.reason).toBe('no_session_id');
    });

    test('skips sync when session not joined', async () => {
      const mockState = {
        activeSession: {
          sessionId: 'session-123',
          hasJoined: false,
          userTracks: [],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      mockGetState.mockReturnValue(mockState);

      const result = await syncTracksToServer('session-123', mockClientId)(mockDispatch, mockGetState);

      expect(putTrackSyncChange).not.toHaveBeenCalled();
      expect(result.skipped).toBe(true);
      expect(result.reason).toBe('session_not_joined');
    });

    test('handles API error gracefully', async () => {
      const mockState = {
        activeSession: {
          sessionId: 'session-123',
          hasJoined: true,
          userTracks: [],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      mockGetState.mockReturnValue(mockState);

      const apiError = new Error('Network error');
      putTrackSyncChange.mockRejectedValue(apiError);

      const result = await syncTracksToServer('session-123', mockClientId)(mockDispatch, mockGetState);

      expect(result.success).toBe(false);
      expect(result.error).toBe(apiError);
    });

    test('dispatches Redux actions on success', async () => {
      const mockState = {
        activeSession: {
          sessionId: 'session-123',
          hasJoined: true,
          userTracks: [],
          backingTracks: [],
          metronome: { isOpen: false }
        }
      };

      mockGetState.mockReturnValue(mockState);

      const apiResponse = {
        tracks: [{ id: 'track-1' }],
        backing_tracks: [{ id: 'backing-1' }]
      };

      putTrackSyncChange.mockResolvedValue(apiResponse);

      await syncTracksToServer('session-123', mockClientId)(mockDispatch, mockGetState);

      // Should dispatch actions to update Redux state
      expect(mockDispatch).toHaveBeenCalled();
    });
  });
});
