import { BehaviorSubject, Observable } from "rxjs";
import { distinctUntilChanged } from "rxjs/operators";
import { Player } from "../player";
import { createLogger, utilsLogger } from "../../../logger";
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
import {
  findPluginByIdentifier,
  loadFeedAndPrefetchThumbnailImage,
  parseTimeToSeconds,
  retrieveFeedUrl,
  retrieveOverlayDuration,
  retrieveOverlayTriggerOffset,
} from "./utils";

export const { log_verbose, log_debug, log_info, log_error } = createLogger({
  category: "ChapterMarkers",
  subsystem: "General",
  parent: utilsLogger,
});

type ActionChapter = {
  type: string;
  options?: {
    title: string;
  };
};

type ChapterMarkerOriginal = {
  id: string;
  title: string;
  start_time: string;
  end_time: string;
  actions: ActionChapter[];
};

export type ChapterMarkerEvent = {
  id: string;
  title: string;
  start_time: number;
  end_time: number;
  actions: ActionChapter[];
};

export type TitleSummaryEvent = {
  title: string | number;
  summary: string | number;
};

type ChapterMarkersObserverProps = {
  player: Player;
};

type PlayNextConfig = {
  entry: ZappEntry;
  duration: number;
  triggerOffset: number; // By default, it's from the end of the video
  offsetIsFromStart: boolean;
};

export type PlayNextState = PlayNextConfig & {
  triggerTime: number;
  handleUserCancelPlayNext: () => void;
};

export class OverlaysObserver {
  readonly chapterSubject: BehaviorSubject<ChapterMarkerEvent>;
  private playNextSubject: BehaviorSubject<PlayNextState>;
  private titleSummarySubject: BehaviorSubject<TitleSummaryEvent>;
  private feedUrl: string;
  private reloadData: () => void;
  private updateTitleAndDescription: (data: any) => void;
  private feedDataInterval: any;
  private releasePlayerObserver?: () => void;
  readonly entry: ZappEntry;
  private chapterMarkerEvents: ChapterMarkerEvent[];
  readonly player: Player;
  private playNextConfig?: PlayNextConfig;
  private isCanceledByUser = false;

  constructor({ player }: ChapterMarkersObserverProps) {
    this.chapterSubject = new BehaviorSubject(null);
    this.playNextSubject = new BehaviorSubject(null);

    this.titleSummarySubject = new BehaviorSubject<TitleSummaryEvent>({
      title: player.getEntry()?.title || "",
      summary: player.getEntry()?.summary || "",
    });

    this.entry = player.getEntry();
    this.player = player;

    this.chapterMarkerEvents = this.prepareChapterMarkers();
    this.releasePlayerObserver = this.subscribeToPlayerEvents();
    this.feedUrl = "";
    this.reloadData = () => {};
    this.updateTitleAndDescription = () => {};
    this.feedDataInterval = null;
    void this.preparePlayNext();
  }

  private setupFeedDataInterval(interval: number) {
    if (this.feedUrl && this.reloadData && this.updateTitleAndDescription) {
      this.feedDataInterval = setInterval(() => {
        this.reloadData();
      }, interval * 1000);
    }
  }

  public clearFeedDataInterval() {
    if (this.feedDataInterval) {
      clearInterval(this.feedDataInterval);
      this.feedDataInterval = null;
    }
  }

  public setFeedDataHandlers(
    feedUrl: string,
    reloadData: () => void,
    interval: number,
    updateTitleAndDescription: (data: any) => void
  ) {
    this.feedUrl = feedUrl;
    this.reloadData = reloadData;
    this.updateTitleAndDescription = updateTitleAndDescription;
    this.setupFeedDataInterval(interval);
  }

  public getTitleSummaryObservable(): Observable<TitleSummaryEvent> {
    return this.titleSummarySubject.asObservable().pipe(distinctUntilChanged());
  }

  handleUserCancelPlayNext = () => {
    this.isCanceledByUser = true;
    this.playNextSubject.next(null);
  };

  preparePlayNext = async () => {
    try {
      const plugins = appStore.get("plugins");

      const playNextPlugin: any = findPluginByIdentifier(
        "QuickBrickPlayNextOverlay",
        plugins
      );

      if (!playNextPlugin) {
        log_debug(
          "preparePlayNext: Play next plugin is not available. Skipping..."
        );

        return;
      }

      const playNextFeedUrl: string = retrieveFeedUrl(this.entry);

      if (!playNextFeedUrl || typeof playNextFeedUrl !== "string") {
        log_debug(
          "preparePlayNext: Play next feed URL is not available. Skipping..."
        );

        return;
      }

      log_debug(
        `preparePlayNext: Loading play next observer with url: ${playNextFeedUrl}`
      );

      const playNextEntry = await loadFeedAndPrefetchThumbnailImage(
        playNextFeedUrl,
        this.entry,
        playNextPlugin
      );

      log_info(
        `preparePlayNext: play next url was successfully loaded title: ${playNextEntry.title} with url: ${playNextFeedUrl}`
      );

      const playNextChapterMarker = this.chapterMarkerEvents?.find((chapter) =>
        chapter.actions?.some((action) => action.type === "show_play_next")
      );

      const chapterDuration = playNextChapterMarker
        ? playNextChapterMarker.end_time - playNextChapterMarker.start_time
        : null;

      this.playNextConfig = {
        entry: playNextEntry,
        duration: chapterDuration || retrieveOverlayDuration(playNextPlugin),
        triggerOffset: playNextChapterMarker
          ? playNextChapterMarker.start_time
          : retrieveOverlayTriggerOffset(this.entry),
        offsetIsFromStart: !!playNextChapterMarker,
      };
    } catch (error) {
      // TODO: need to improve error message handling
      const errorMessage = error?.message
        ? typeof error.message === "function"
          ? error.message()
          : error.message
        : "no readable error message";

      const code = error?.code || "no readable code";

      log_error(
        `preparePlayNext: loading failed with error: ${errorMessage} code: ${code}. Play next observer, will not be executed`,
        {
          error,
        }
      );

      this.playNextSubject.next(null);
    }
  };

  // TODO: Hack for video end, will be replaced with playlist prev/next in the future
  getPlayNextEntry = () =>
    !this.isCanceledByUser ? this.playNextConfig?.entry : null;

  prepareChapterMarkers = () => {
    const chapterMarkers: ChapterMarkerOriginal[] =
      this.entry?.extensions?.chapter_markers?.chapters;

    if (!chapterMarkers || !Array.isArray(chapterMarkers)) {
      return [];
    }

    try {
      const events = chapterMarkers.map((chapter: ChapterMarkerOriginal) => {
        const startTime = parseTimeToSeconds(chapter.start_time);
        const endTime = parseTimeToSeconds(chapter.end_time);

        return {
          ...chapter,
          start_time: startTime,
          end_time: endTime,
        };
      });

      return events;
    } catch (error) {
      // TODO: need to improve error message handling
      const errorMessage = error?.message
        ? typeof error.message === "function"
          ? error.message()
          : error.message
        : "no readable error message";

      const code = error?.code || "no readable code";

      log_error(
        `prepareChapterMarkers: preparation failed with error: ${errorMessage} code: ${code}.`,
        {
          error,
          chapterMarkers,
        }
      );

      return [];
    }
  };

  checkPlayNext = (event: QuickBrickPlayer.OnTimeUpdateEvent) => {
    if (!this.playNextConfig) {
      return;
    }

    if (this.player.isAd()) {
      return;
    }

    if (this.player.isLive()) {
      return;
    }

    const currentTime = event.currentTime;

    if (!currentTime) {
      return;
    }

    const duration = event.duration;

    const triggerTime =
      Math.min(
        Math.abs(duration - this.playNextConfig.duration),
        Math.abs(
          this.playNextConfig.offsetIsFromStart
            ? this.playNextConfig.triggerOffset
            : duration - this.playNextConfig.triggerOffset
        )
      ) - 1;

    if (!triggerTime) {
      return;
    }

    const shouldPlayNextBeVisible = currentTime > triggerTime;

    if (shouldPlayNextBeVisible) {
      if (this.isCanceledByUser) {
        return;
      }

      this.playNextSubject.next({
        ...this.playNextConfig,
        triggerTime,
        handleUserCancelPlayNext: this.handleUserCancelPlayNext,
      });
    } else {
      this.playNextSubject.next(null);
      this.isCanceledByUser = false;
    }
  };

  onVideoProgress = (event: QuickBrickPlayer.OnTimeUpdateEvent) => {
    const currentTime = event.currentTime;

    const currentChapter = this.chapterMarkerEvents.find((chapter) => {
      const startTime = chapter.start_time;
      const endTime = chapter.end_time;

      return currentTime >= startTime && currentTime <= endTime;
    });

    // Play next
    this.checkPlayNext(event);

    // Chapter Markers
    this.onChapterReached(currentChapter || null);
  };

  onPlayerClose = () => {
    this.chapterSubject.complete();
    this.playNextSubject.complete();
    this.titleSummarySubject.complete();
    this.releasePlayerObserver?.();
    this.clearFeedDataInterval();
    this.releasePlayerObserver = null;
  };

  subscribeToPlayerEvents = () =>
    this.player.addListener({
      id: "chapterMarkersObserver",
      listener: {
        onVideoProgress: this.onVideoProgress,
        onPlayerClose: this.onPlayerClose,
      },
    });

  public onChapterReached = (chapterEvent: ChapterMarkerEvent) => {
    this.chapterSubject.next(chapterEvent);
  };

  public getChapterMarkerObservable = (): Observable<ChapterMarkerEvent> => {
    if (!this.chapterSubject) {
      return null;
    }

    // https://www.learnrxjs.io/learn-rxjs/operators/filtering/distinctuntilchanged
    return this.chapterSubject.pipe(distinctUntilChanged());
  };

  // https://www.learnrxjs.io/learn-rxjs/operators/filtering/distinctuntilchanged
  public getPlayNextObservable = (): Observable<PlayNextState> =>
    this.playNextSubject.pipe(
      distinctUntilChanged(
        (prev, curr) => prev?.triggerTime === curr?.triggerTime
      )
    );
}
