import { afterEach, describe, expect, it, vi } from "vitest";
import type { IManifestMetadata } from "../../../../manifest";
import type {
  IPlaybackObservation,
  IReadOnlyPlaybackObserver,
} from "../../../../playback_observer";
import { SeekingState } from "../../../../playback_observer";
import SharedReference from "../../../../utils/reference";
import type { CancellationSignal } from "../../../../utils/task_canceller";
import TaskCanceller from "../../../../utils/task_canceller";
import StreamEventsEmitter from "../stream_events_emitter/stream_events_emitter";

describe("init - StreamEventsEmitter", () => {
  afterEach(() => {
    vi.useRealTimers();
  });

  it("should not send skipped events for positions crossed while the media is not ready", () => {
    vi.useFakeTimers();

    const observationRef = new SharedReference(generateObservation(4));
    const playbackObserver = createPlaybackObserver(observationRef);
    const streamEventsEmitter = new StreamEventsEmitter(playbackObserver);
    const skippedEvents: unknown[] = [];
    const regularEvents: unknown[] = [];
    const stopCanceller = new TaskCanceller("test");

    streamEventsEmitter.addEventListener(
      "event",
      (evt) => {
        regularEvents.push(evt);
      },
      stopCanceller.signal,
    );
    streamEventsEmitter.addEventListener(
      "eventSkip",
      (evt) => {
        skippedEvents.push(evt);
      },
      stopCanceller.signal,
    );

    streamEventsEmitter.start(generateManifest());
    streamEventsEmitter.pause();

    observationRef.setValue(generateObservation(9));
    vi.advanceTimersByTime(200);

    streamEventsEmitter.resume();
    observationRef.setValue(generateObservation(9.1));
    vi.advanceTimersByTime(200);

    expect(regularEvents).toHaveLength(0);
    expect(skippedEvents).toHaveLength(0);

    streamEventsEmitter.stop("test end");
    stopCanceller.cancel("test end");
  });

  it("should compare from the current position immediately after resuming", () => {
    vi.useFakeTimers();

    const observationRef = new SharedReference(generateObservation(4));
    const playbackObserver = createPlaybackObserver(observationRef);
    const streamEventsEmitter = new StreamEventsEmitter(playbackObserver);
    const regularEvents: unknown[] = [];
    const stopCanceller = new TaskCanceller("test");

    streamEventsEmitter.addEventListener(
      "event",
      (evt) => {
        regularEvents.push(evt);
      },
      stopCanceller.signal,
    );

    streamEventsEmitter.start(generateManifest());
    streamEventsEmitter.pause();

    observationRef.setValue(generateObservation(4.5));
    streamEventsEmitter.resume();
    observationRef.setValue(generateObservation(8.5));
    vi.advanceTimersByTime(200);

    expect(regularEvents).toHaveLength(1);

    streamEventsEmitter.stop("test end");
    stopCanceller.cancel("test end");
  });
});

function createPlaybackObserver(
  observationRef: SharedReference<IPlaybackObservation>,
): IReadOnlyPlaybackObserver<IPlaybackObservation> {
  return {
    getCurrentTime() {
      return observationRef.getValue().position.getPolled();
    },
    getPlaybackRate() {
      return 1;
    },
    getReadyState() {
      return 4;
    },
    getIsPaused() {
      return false;
    },
    getReference() {
      return observationRef;
    },
    listen(
      cb: (observation: IPlaybackObservation, stopListening: () => void) => void,
      options: {
        includeLastObservation?: boolean | undefined;
        clearSignal: CancellationSignal;
      },
    ) {
      observationRef.onUpdate(cb, {
        clearSignal: options.clearSignal,
        emitCurrentValue: options.includeLastObservation,
      });
    },
    deriveReadOnlyObserver() {
      throw new Error("unused in this test");
    },
  };
}

function generateManifest(): IManifestMetadata {
  return {
    periods: [
      {
        start: 0,
        streamEvents: [
          {
            start: 5,
            end: 8,
            id: "evt",
            data: {
              type: "dash-event-stream",
              value: {
                schemeIdUri: "urn:test",
                timescale: 1,
                xmlData: { data: "<Event />", namespaces: [] },
              },
            },
          },
        ],
      },
    ],
  } as unknown as IManifestMetadata;
}

function generateObservation(currentTime: number): IPlaybackObservation {
  return {
    event: "timeupdate",
    seeking: SeekingState.None,
    rebuffering: null,
    freezing: null,
    duration: 100,
    ended: false,
    paused: false,
    playbackRate: 1,
    readyState: 4,
    bufferGap: 10,
    fullyLoaded: false,
    currentRange: { start: 0, end: 20 },
    buffered: {} as TimeRanges,
    position: {
      getPolled() {
        return currentTime;
      },
    } as IPlaybackObservation["position"],
  };
}
