/* istanbul ignore file */
import * as zocksSdk from '@zocks-communications/client-sdk-js'
import {
  ConnectionQuality,
  LocalParticipant,
  LocalMediaStream,
  RemoteParticipant,
  Session,
  StreamKind,
  StreamSource,
  Track,
  ZocksVideo,
  Clients,
  RoomInfo,
  VirtualBackground,
  VirtualBackgroundType,
  MediaClient,
  TranscriptionService,
} from '@zocks-communications/client-sdk-js'
import { mock } from 'jest-mock-extended'
import { ViewModel } from './store/view/models'
import { RootState, zocksStoreConfig } from './app/store'
import { configureStore } from '@reduxjs/toolkit'
import {
  activeSpeakersChanged,
  addRemoteParticipant,
  setLocalParticipantId,
  setLocalParticipantName,
} from './store/session/sessionSlice'
import {
  ChatModel,
  ParticipantModel,
  SessionModel,
  SessionTranscriptModel,
  VirtualBackgroundModel,
  MediaDeviceModel,
} from './store'

export const setupZocksSdkMocks = () => {
  const mockLocalAudioTrack = mock<Track>({
    id: 'testLocalAudioTrackId',
    source: StreamSource.MICROPHONE,
    kind: StreamKind.AUDIO,
    isAvailable: true,
    isMuted: false,
    isSubscribed: true,
  })
  const mockLocalVideoTrack = mock<Track>({
    id: 'testLocalVideoTrackId',
    source: StreamSource.CAMERA,
    kind: StreamKind.VIDEO,
    isAvailable: true,
    isMuted: false,
    isSubscribed: true,
  })
  const mockRemoteAudioTrack = mock<Track>({
    id: 'testRemoteAudioTrackId',
    source: StreamSource.MICROPHONE,
    kind: StreamKind.AUDIO,
    isAvailable: true,
    isMuted: false,
    isSubscribed: true,
  })
  const mockRemoteVideoTrack = mock<Track>({
    id: 'testRemoteVideoTrackId',
    source: StreamSource.CAMERA,
    kind: StreamKind.VIDEO,
    isAvailable: true,
    isMuted: false,
    isSubscribed: true,
  })
  const mockLocalScreenShareTrack = mock<Track>({
    id: 'testLocalScreenShareTrackId',
    source: StreamSource.SCREEN_SHARE,
    kind: StreamKind.VIDEO,
    isAvailable: true,
    isMuted: false,
    isSubscribed: true,
  })
  const mockRemoteScreenShareTrack = mock<Track>({
    id: 'testRemoteScreenShareTrackId',
    source: StreamSource.SCREEN_SHARE,
    kind: StreamKind.VIDEO,
    isAvailable: true,
    isMuted: false,
    isSubscribed: true,
  })

  const mockLocalParticipant = mock<LocalParticipant>({
    id: 'testLocalParticipantId',
    name: 'testLocalParticipantName',
    tracks: [mockLocalAudioTrack, mockLocalVideoTrack, mockLocalScreenShareTrack],
  })

  const mockRemoteParticipant = mock<RemoteParticipant>({
    id: 'testRemoteParticipantId',
    name: 'testRemoteParticipantName',
    tracks: [mockRemoteAudioTrack, mockRemoteVideoTrack, mockRemoteScreenShareTrack],
  })

  const remoteParticipants = new Map<string, RemoteParticipant>([[mockRemoteParticipant.id, mockRemoteParticipant]])
  const mockRemoteParticipants = mock<Map<string, RemoteParticipant>>()
  mockRemoteParticipants.get.mockImplementation((participantId: string) => remoteParticipants.get(participantId))
  mockRemoteParticipants.set.mockImplementation((participantId: string, remoteParticipant: RemoteParticipant) =>
    remoteParticipants.set(participantId, remoteParticipant)
  )
  mockRemoteParticipants.values.mockImplementation(() => remoteParticipants.values())
  mockRemoteParticipants.has.mockImplementation((participantId: string) => remoteParticipants.has(participantId))

  const mockTranscriptionService = mock<TranscriptionService>()
  const mockSession = mock<Session>({
    room: {
      id: 'testRoomId',
      name: 'testRoomName',
    },
    localParticipant: mockLocalParticipant,
    participants: mockRemoteParticipants,
    transcriptionService: mockTranscriptionService,
  })

  const mockApi = setupApiMock()
  const mockZocksVideo = mock<ZocksVideo>({ API: mockApi })

  mockZocksVideo.createNewSession.mockReturnValue(mockSession)

  const mockGetSessionByRoomId = jest.spyOn(zocksSdk.ZocksSDK, 'getSessionById').mockReturnValue(mockSession)
  const mockInitSdk = jest.spyOn(zocksSdk.ZocksSDK, 'init').mockReturnValue(mockZocksVideo)

  const events = {}
  const mockEventBus = {
    // @ts-ignore
    on: jest.fn().mockImplementation((evt, callback) => (events[evt] = callback)),
    // @ts-ignore
    emit: jest.fn().mockImplementation((evt, ...args) => events[evt](...args)),
  }

  mockSession.on.mockImplementation((evt, callback) => mockEventBus.on(evt, callback))
  mockLocalParticipant.on.mockImplementation((evt, callback) => mockEventBus.on(evt, callback))

  mockLocalParticipant.publishScreenShare.mockReturnValue(Promise.resolve())

  const mockStore = {
    session: {
      roomId: mockSession.room.id,
      localParticipant: {
        id: mockLocalParticipant.id,
        name: mockLocalParticipant.name,
        tracks: {
          audioTracks: [
            {
              id: mockLocalAudioTrack.id,
              isMuted: mockLocalAudioTrack.isMuted,
              isSubscribed: mockLocalAudioTrack.isSubscribed,
            },
          ],
          videoTracks: [
            {
              id: mockLocalVideoTrack.id,
              isMuted: mockLocalVideoTrack.isMuted,
              isSubscribed: mockLocalVideoTrack.isSubscribed,
            },
          ],
          screenShareTrack: {
            id: mockLocalScreenShareTrack.id,
            isMuted: mockLocalScreenShareTrack.isMuted,
            isSubscribed: mockLocalScreenShareTrack.isSubscribed,
          },
        },
        connectionQuality: ConnectionQuality.EXCELLENT,
      },
      remoteParticipants: {
        [mockRemoteParticipant.id]: {
          id: mockRemoteParticipant.id,
          name: mockRemoteParticipant.name,
          tracks: {
            audioTracks: [
              {
                id: mockRemoteAudioTrack.id,
                isMuted: mockRemoteAudioTrack.isMuted,
                isSubscribed: mockRemoteAudioTrack.isSubscribed,
              },
            ],
            videoTracks: [
              {
                id: mockRemoteVideoTrack.id,
                isMuted: mockRemoteVideoTrack.isMuted,
                isSubscribed: mockRemoteVideoTrack.isSubscribed,
              },
            ],
            screenShareTrack: {
              id: mockRemoteScreenShareTrack.id,
              isMuted: mockRemoteScreenShareTrack.isMuted,
              isSubscribed: mockRemoteScreenShareTrack.isSubscribed,
            },
          },
          connectionQuality: ConnectionQuality.EXCELLENT,
        },
      },
    },
  }

  return {
    mockInitSdk,
    mockGetSessionByRoomId,
    mockApi,
    mockSession,
    mockLocalParticipant,
    mockRemoteParticipant,
    mockLocalAudioTrack,
    mockLocalVideoTrack,
    mockLocalScreenShareTrack,
    mockRemoteAudioTrack,
    mockRemoteVideoTrack,
    mockRemoteScreenShareTrack,
    mockEventBus,
    mockStore,
  }
}

const setupApiMock = () => {
  const mockApiMediaClient = mock<MediaClient>({ createRoom: jest.fn().mockResolvedValue(mock<RoomInfo>()) })

  return mock<Clients>({ media: mockApiMediaClient })
}

export const createMockParticipants = (n: number): ParticipantModel[] =>
  Array.from({ length: n }, (x, i) => {
    return {
      id: `Participant id: ${i.toString(10)}`,
      name: `Participant name: ${i.toString(10)}`,
      tracks: {
        videoTracks: [],
        audioTracks: [],
        screenShareTrack: null,
      },
      connectionQuality: ConnectionQuality.UNKNOWN,
    }
  })

export const initStore = (numberOfRemoteParticipants: number, activeSpeakers?: string[]) => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setLocalParticipantId('Participant id: local'))
  store.dispatch(setLocalParticipantName('Participant name: local'))
  const mockRemoteParticipants = createMockParticipants(numberOfRemoteParticipants)
  mockRemoteParticipants.forEach((participant) =>
    store.dispatch(addRemoteParticipant({ participantId: participant.id!, participantName: participant.name! }))
  )
  if (activeSpeakers) {
    store.dispatch(activeSpeakersChanged(activeSpeakers))
  }

  return store
}

export const setupMediaDevicesMock = (numberOfAudioDevices = 2, numberOfVideoDevices = 2) => {
  const mockNavigatorMediaDevices = { addEventListener: jest.fn(), removeEventListener: jest.fn() }
  const mockMediaDevices: MediaDeviceInfo[] = []

  for (const i of [...Array(numberOfAudioDevices).keys()]) {
    mockMediaDevices.push({
      deviceId: `testMicrophone${i + 1}MediaDeviceId`,
      label: `Test Microphone ${i + 1}`,
      kind: 'audioinput',
      groupId: 'test',
      toJSON: jest.fn(),
    })
  }
  for (const i of [...Array(numberOfVideoDevices).keys()]) {
    mockMediaDevices.push({
      deviceId: `testCamera${i + 1}MediaDeviceId`,
      label: `Test Camera ${i + 1}`,
      kind: 'videoinput',
      groupId: 'test',
      toJSON: jest.fn(),
    })
  }

  const mockGetDevices = jest
    .spyOn(zocksSdk.MediaDeviceManager, 'getDevices')
    .mockImplementation((kind) => Promise.resolve(mockMediaDevices.filter((d) => d.kind === kind)))
  Object.defineProperty(window.navigator, 'mediaDevices', {
    writable: true,
    value: mockNavigatorMediaDevices,
  })

  return { mockGetDevices, mockMediaDevices, mockNavigatorMediaDevices }
}

export const setupMockLocalMediaStream = (deviceId?: string) => {
  const mockLocalMediaStream = mock<LocalMediaStream>({
    mediaStreamTrack: mock<MediaStreamTrack>({
      getSettings: jest.fn().mockImplementation(() => ({ deviceId })),
    }),
  })
  const mockLocalMediaStreamFromTrack = mock<LocalMediaStream>({
    mediaStreamTrack: mock<MediaStreamTrack>({
      getSettings: jest.fn().mockImplementation(() => ({ deviceId })),
    }),
  })
  const mockCreateMediaStream = jest
    .spyOn(LocalMediaStream, 'create')
    .mockReturnValue(Promise.resolve(mockLocalMediaStream))

  const mockCreateMediaStreamFromTrack = jest
    .spyOn(LocalMediaStream, 'createFromMediaStreamTrack')
    .mockReturnValue(mockLocalMediaStreamFromTrack)

  return { mockLocalMediaStream, mockLocalMediaStreamFromTrack, mockCreateMediaStream, mockCreateMediaStreamFromTrack }
}

export const setupMockVirtualBackground = () => {
  const mockVirtualBackgroundStream = mock<MediaStream>()
  const mockVirtualBackgroundStreamTrack = mock<MediaStreamTrack>()
  const mockStartVirtualBackground = jest.fn()
  const mockStopVirtualBackground = jest.fn()
  const mockVirtualBackground = mock<VirtualBackground>({
    getType(): VirtualBackgroundType {
      return VirtualBackgroundType.BLUR
    },
    start(): void | Promise<void> {
      mockStartVirtualBackground()
    },
    stop(): void | Promise<void> {
      mockStopVirtualBackground()
    },
  })

  Object.defineProperty(mockVirtualBackground, 'outputStream', {
    writable: false,
    configurable: true,
    value: mockVirtualBackgroundStream
  })

  Object.defineProperty(mockVirtualBackground, 'outputStreamTrack', {
    writable: false,
    configurable: true,
    value: mockVirtualBackgroundStreamTrack
  })

  const mockCreateVirtualBackground = jest
    .spyOn(zocksSdk, 'createVirtualBackground')
    .mockReturnValue(mockVirtualBackground)

  return {
    mockCreateVirtualBackground,
    mockVirtualBackground,
    mockVirtualBackgroundStream,
    mockVirtualBackgroundStreamTrack,
    mockStartVirtualBackground,
    mockStopVirtualBackground,
  }
}

export const mockRootState: RootState = {
  chat: mock<ChatModel>(),
  mediaDevice: mock<MediaDeviceModel>(),
  session: mock<SessionModel>(),
  transcript: mock<SessionTranscriptModel>(),
  view: mock<ViewModel>(),
  virtualBackground: mock<VirtualBackgroundModel>(),
}
