// Fake Jam Client Recordings - simulates recording functionality for testing
export class FakeJamClientRecordings {
  constructor(app, fakeJamClient, p2pMessageFactory) {
    this.logger = console;
    this.app = app;
    this.fakeJamClient = fakeJamClient;
    this.p2pMessageFactory = p2pMessageFactory;

    this.startRecordingResultCallbackName = null;
    this.stopRecordingResultCallbackName = null;
    this.startedRecordingResultCallbackName = null;
    this.stoppedRecordingEventCallbackName = null;
    this.abortedRecordingEventCallbackName = null;

    this.startingSessionState = null;
    this.stoppingSessionState = null;

    this.currentRecordingId = null;
    this.currentRecordingCreatorClientId = null;
    this.currentRecordingClientIds = null;

    // Register P2P callbacks
    const callbacks = {};
    callbacks[this.p2pMessageFactory.Types.START_RECORDING] = this.onStartRecording.bind(this);
    callbacks[this.p2pMessageFactory.Types.START_RECORDING_ACK] = this.onStartRecordingAck.bind(this);
    callbacks[this.p2pMessageFactory.Types.STOP_RECORDING] = this.onStopRecording.bind(this);
    callbacks[this.p2pMessageFactory.Types.STOP_RECORDING_ACK] = this.onStopRecordingAck.bind(this);
    callbacks[this.p2pMessageFactory.Types.ABORT_RECORDING] = this.onAbortRecording.bind(this);
    fakeJamClient.RegisterP2PMessageCallbacks(callbacks);
  }

  timeoutStartRecordingTimer() {
    eval(this.startRecordingResultCallbackName).call(this, this.startingSessionState.recordingId, { success: false, reason: 'client-no-response', detail: this.startingSessionState.groupedClientTracks[0] });
    this.startingSessionState = null;
  }

  timeoutStopRecordingTimer() {
    eval(this.stopRecordingResultCallbackName).call(this, this.stoppingSessionState.recordingId, { success: false, reason: 'client-no-response', detail: this.stoppingSessionState.groupedClientTracks[0] });
  }

  StartRecording(recordingId, clients) {
    this.startingSessionState = {};

    // Expect all clients to respond within 1 second to mimic reliable UDP layer
    this.startingSessionState.aggregatingStartResultsTimer = setTimeout(() => this.timeoutStartRecordingTimer(), 1000);
    this.startingSessionState.recordingId = recordingId;
    this.startingSessionState.groupedClientTracks = this.copyClientIds(clients, this.app.clientId);

    // Store current recording data
    this.currentRecordingId = recordingId;
    this.currentRecordingCreatorClientId = this.app.clientId;
    this.currentRecordingClientIds = this.copyClientIds(clients, this.app.clientId);

    if (this.startingSessionState.groupedClientTracks.length === 0) {
      // If no clients but 'self', declare successful recording immediately
      this.finishSuccessfulStart(recordingId);
    } else {
      // Signal all other connected clients that recording has started
      for (const clientId of this.startingSessionState.groupedClientTracks) {
        this.fakeJamClient.SendP2PMessage(clientId, JSON.stringify(this.p2pMessageFactory.startRecording(recordingId)));
      }
    }
  }

  StopRecording(recordingId, clients, result) {
    if (this.startingSessionState) {
      // Currently starting a session
      // TODO
    }

    if (!result) {
      result = { success: true };
    }

    this.stoppingSessionState = {};

    // Expect all clients to respond within 1 second
    this.stoppingSessionState.aggregatingStopResultsTimer = setTimeout(() => this.timeoutStopRecordingTimer(), 1000);
    this.stoppingSessionState.recordingId = recordingId;
    this.stoppingSessionState.groupedClientTracks = this.copyClientIds(clients, this.app.clientId);

    if (this.stoppingSessionState.groupedClientTracks.length === 0) {
      this.finishSuccessfulStop(recordingId);
    } else {
      // Signal all other connected clients that recording has stopped
      for (const clientId of this.stoppingSessionState.groupedClientTracks) {
        this.fakeJamClient.SendP2PMessage(clientId, JSON.stringify(this.p2pMessageFactory.stopRecording(recordingId, result.success, result.reason, result.detail)));
      }
    }
  }

  AbortRecording(recordingId, errorReason, errorDetail) {
    // TODO: check recordingId
    this.fakeJamClient.SendP2PMessage(this.currentRecordingCreatorClientId, JSON.stringify(this.p2pMessageFactory.abortRecording(recordingId, errorReason, errorDetail)));
  }

  onStartRecording(from, payload) {
    this.logger.debug("received start recording request from " + from);
    if (window.JK?.SessionStore?.isRecording) {
      // Reject the request
      this.fakeJamClient.SendP2PMessage(from, JSON.stringify(this.p2pMessageFactory.startRecordingAck(payload.recordingId, false, "already-recording", null)));
    } else {
      // Accept and tell frontend we are recording
      this.currentRecordingId = payload.recordingId;
      this.currentRecordingCreatorClientId = from;

      this.fakeJamClient.SendP2PMessage(from, JSON.stringify(this.p2pMessageFactory.startRecordingAck(payload.recordingId, true, null, null)));
      eval(this.startedRecordingResultCallbackName).call(this, payload.recordingId, { success: true }, from);
    }
  }

  onStartRecordingAck(from, payload) {
    this.logger.debug("received start recording ack from " + from);

    if (this.startingSessionState) {
      if (payload.success) {
        const index = this.startingSessionState.groupedClientTracks.indexOf(from);
        this.startingSessionState.groupedClientTracks.splice(index, 1);

        if (this.startingSessionState.groupedClientTracks.length === 0) {
          this.finishSuccessfulStart(payload.recordingId);
        }
      } else {
        // TODO: handle error
        this.logger.warn("received unsuccessful start_record_ack from: " + from);
      }
    } else {
      this.logger.warn("received start_record_ack when no recording starting from: " + from);
    }
  }

  onStopRecording(from, payload) {
    this.logger.debug("received stop recording request from " + from);

    // TODO: check recordingId and if currently recording
    this.fakeJamClient.SendP2PMessage(from, JSON.stringify(this.p2pMessageFactory.stopRecordingAck(payload.recordingId, true)));

    if (this.stopRecordingResultCallbackName) {
      eval(this.stopRecordingResultCallbackName).call(this, payload.recordingId, { success: payload.success, reason: payload.reason, detail: from });
    }
  }

  onStopRecordingAck(from, payload) {
    this.logger.debug("received stop recording ack from " + from);

    if (this.stoppingSessionState) {
      if (payload.success) {
        const index = this.stoppingSessionState.groupedClientTracks.indexOf(from);
        this.stoppingSessionState.groupedClientTracks.splice(index, 1);

        if (this.stoppingSessionState.groupedClientTracks.length === 0) {
          this.finishSuccessfulStop(payload.recordingId);
        }
      } else {
        // TODO: handle error
        this.logger.error("client responded with error: ", payload);
      }
    } else {
      // TODO: error case
    }
  }

  onAbortRecording(from, payload) {
    this.logger.debug("received abort recording from " + from);

    // TODO: check if currently recording and matches payload.recordingId

    if (this.app.clientId === this.currentRecordingCreatorClientId) {
      // Ask frontend to stop
      for (const clientId of this.currentRecordingClientIds) {
        this.fakeJamClient.SendP2PMessage(clientId, JSON.stringify(this.p2pMessageFactory.abortRecording(this.currentRecordingId, payload.reason, from)));
      }
    } else {
      this.logger.debug("only creator deals with abort request. sent from:" + from + " reason: " + payload.errorReason);
    }

    eval(this.abortedRecordingEventCallbackName).call(this, payload.recordingId, { success: payload.success, reason: payload.reason, detail: from });
  }

  RegisterRecordingCallbacks(startRecordingCallbackName, stopRecordingCallbackName, startedRecordingCallbackName, stoppedRecordingCallbackName, abortedRecordingCallbackName) {
    this.startRecordingResultCallbackName = startRecordingCallbackName;
    this.stopRecordingResultCallbackName = stopRecordingCallbackName;
    this.startedRecordingResultCallbackName = startedRecordingCallbackName;
    this.stoppedRecordingEventCallbackName = stoppedRecordingCallbackName;
    this.abortedRecordingEventCallbackName = abortedRecordingCallbackName;
  }

  copyClientIds(clientIds, myClientId) {
    const newClientIds = [];
    for (const clientId of clientIds) {
      if (clientId !== myClientId) {
        newClientIds.push(clientId);
      }
    }
    return newClientIds;
  }

  finishSuccessfulStart(recordingId) {
    clearTimeout(this.startingSessionState.aggregatingStartResultsTimer);
    this.startingSessionState = null;
    eval(this.startRecordingResultCallbackName).call(this, recordingId, { success: true });
  }

  finishSuccessfulStop(recordingId, errorReason) {
    clearTimeout(this.stoppingSessionState.aggregatingStopResultsTimer);
    this.stoppingSessionState = null;
    const result = { success: true };
    if (errorReason) {
      result.success = false;
      result.reason = errorReason;
      result.detail = "";
    }
    eval(this.stopRecordingResultCallbackName).call(this, recordingId, result);
  }
}
