const configClass = require("./config");
const { axios } = require("./axios");

class EmbyAPI {
  constructor() {
    this.config = null;
    this.configReady = false;
    this.#checkReadyStatus();
    this.sessionErrorCounter = 0;
  }
  //Helper classes
  #checkReadyStatus() {
    let checkConfigError = setInterval(async () => {
      const success = await this.#fetchConfig();
      if (success) {
        clearInterval(checkConfigError);
      }
    }, 5000); // Check every 5 seconds
  }

  async #fetchConfig() {
    const _config = await new configClass().getConfig();
    if (!_config.error && _config.state === 2) {
      this.config = _config;
      this.configReady = true;
      return true;
    }
    return false;
  }
  #errorHandler(error, url) {
    if (error.response) {
      console.log("[EMBY-API]: " + this.#httpErrorMessageHandler(error));
    } else {
      console.log("[EMBY-API]", {
        ErrorAt: this.#getErrorLineNumber(error),
        ErrorLines: this.#getErrorLineNumbers(error),
        Message: error.message,
        url: url,
        // StackTrace: this.#getStackTrace(error),
      });
    }
  }

  #httpErrorMessageHandler(error) {
    let message = "";
    switch (error.response.status) {
      case 400:
        message = "400 Bad Request";
        break;
      case 401:
        message = "401 Unauthorized";
        break;
      case 403:
        message = "403 Access Forbidden";
        break;
      case 404:
        message = `404 URL Not Found : ${error.request.path}`;
        break;
      case 503:
        message = `503 Service Unavailable : ${error.request.path}`;
        break;
      default:
        message = `Unexpected status code: ${error.response.status}`;
    }
    return message;
  }

  #getErrorLineNumber(error) {
    const stackTrace = this.#getStackTrace(error);
    if (stackTrace.length < 1) {
      return "Unknown";
    }
    const errorLine = stackTrace[1].trim();
    const lineNumber = errorLine.substring(errorLine.lastIndexOf("\\") + 1, errorLine.lastIndexOf(")"));
    return lineNumber;
  }

  #getErrorLineNumbers(error) {
    const stackTrace = this.#getStackTrace(error);
    let errorLines = [];

    for (const [index, line] of stackTrace.entries()) {
      if (line.trim().startsWith("at")) {
        const errorLine = line.trim();
        const startSubstring = errorLine.lastIndexOf("\\") == -1 ? errorLine.indexOf("(") + 1 : errorLine.lastIndexOf("\\") + 1;
        const endSubstring = errorLine.lastIndexOf(")") == -1 ? errorLine.length : errorLine.lastIndexOf(")");
        const lineNumber = errorLine.substring(startSubstring, endSubstring);
        errorLines.push({ TraceIndex: index, line: lineNumber });
      }
    }

    return errorLines;
  }

  #getStackTrace(error) {
    if (!error.stack) return [];
    const stackTrace = error.stack.split("\n");
    return stackTrace;
  }

  #delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  //Functions

  async getUsers(refreshConfig = false) {
    if (!this.configReady || refreshConfig) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      const url = `${this.config.JF_HOST}/Users`;

      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
      });

      if (Array.isArray(response?.data)) {
        return response?.data || [];
      }

      console.log("[JELLYFIN-API] : getUsers - " + (response?.data || response));

      return [];
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getAdmins(refreshConfig = false) {
    try {
      const users = await this.getUsers(refreshConfig);
      return users?.filter((user) => user.Policy.IsAdministrator) || [];
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getItemsByID({ ids, params }) {
    if (!this.configReady) {
      return [];
    }
    try {
      let url = `${this.config.JF_HOST}/Items?ids=${ids}`;
      let startIndex = params && params.startIndex ? params.startIndex : 0;
      let increment = params && params.increment ? params.increment : 200;
      let limit = params && params.limit !== undefined ? params.limit : increment;
      let recursive = params && params.recursive !== undefined ? params.recursive : true;
      let total = 200;

      let final_response = [];
      while (startIndex < total || total === undefined) {
        const response = await axios.get(url, {
          headers: {
            "X-MediaBrowser-Token": this.config.JF_API_KEY,
          },
          params: {
            fields: "MediaSources,DateCreated,Genres",
            startIndex: startIndex,
            recursive: recursive,
            limit: limit,
            isMissing: false,
            excludeLocationTypes: "Virtual",
          },
        });

        total = response?.data?.TotalRecordCount ?? 0;
        startIndex += increment;

        const result = response?.data?.Items || [];

        final_response.push(...result);
        if (response.data.TotalRecordCount === undefined || final_response.length >= limit) {
          break;
        }

        await this.#delay(10);
      }

      return final_response;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getItemsFromParentId({ id, itemid, params, ws, syncTask, wsMessage }) {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      let url = `${this.config.JF_HOST}/Items?ParentId=${id}`;

      let userid;
      if (!userid || userid == null) {
        await new configClass().getPreferedAdmin().then(async (adminid) => {
          if (!adminid || adminid == null) {
            userid = (await this.getAdmins())[0].Id;
          } else {
            userid = adminid;
          }
        });
      }
      url += `&userId=${userid}`;

      if (itemid && itemid != null) {
        url += `&Ids=${itemid}`;
      }

      let startIndex = params && params.startIndex !== undefined ? params.startIndex : 0;
      let increment = params && params.increment !== undefined ? params.increment : 200;
      let recursive = params && params.recursive !== undefined ? params.recursive : true;
      let limit = params && params.limit !== undefined ? params.limit : increment;
      let total = startIndex + increment;

      let AllItems = [];
      while (startIndex < total || total === undefined) {
        const response = await axios.get(url, {
          headers: {
            "X-MediaBrowser-Token": this.config.JF_API_KEY,
          },
          params: {
            fields: "MediaSources,DateCreated,Genres",
            startIndex: startIndex,
            recursive: recursive,
            limit: limit,
            isMissing: false,
            excludeLocationTypes: "Virtual",
            sortBy: "DateCreated",
            sortOrder: "Descending",
          },
        });

        total = response?.data?.TotalRecordCount || 0;
        startIndex += increment;

        const result = response?.data?.Items || [];

        AllItems.push(...result);

        if (ws && syncTask && wsMessage) {
          ws(syncTask.wsKey, {
            type: "Update",
            message: `${wsMessage} - ${((Math.min(startIndex, total) / total) * 100).toFixed(2)}%`,
          });
        }

        if (
          response.data.TotalRecordCount === undefined ||
          (params && params.startIndex !== undefined) ||
          AllItems.length >= limit
        ) {
          break;
        }

        await this.#delay(10);
      }

      return AllItems;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getItemInfo({ itemID, userid }) {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      if (!userid || userid == null) {
        await new configClass().getPreferedAdmin().then(async (adminid) => {
          if (!adminid || adminid == null) {
            userid = (await this.getAdmins())[0].Id;
          } else {
            userid = adminid;
          }
        });
      }

      let url = `${this.config.JF_HOST}/Items/${itemID}/playbackinfo?userId=${userid}`;

      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
      });

      return response?.data?.MediaSources || 0;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getLibraries() {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      let url = `${this.config.JF_HOST}/Library/MediaFolders`;

      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
      });

      const libraries =
        response?.data?.Items?.filter((library) => !["boxsets", "playlists"].includes(library.CollectionType)) || [];

      return libraries;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getSeasons(SeriesId) {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    let url = `${this.config.JF_HOST}/Shows/${SeriesId}/Seasons`;
    try {
      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
      });

      return response?.data?.Items?.filter((item) => item.LocationType !== "Virtual") || [];
    } catch (error) {
      this.#errorHandler(error, url);
      return [];
    }
  }

  async getEpisodes({ SeriesId, SeasonId }) {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      let url = `${this.config.JF_HOST}/Shows/${SeriesId}/Episodes?seasonId=${SeasonId}`;

      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
      });

      return response?.data?.Items?.filter((item) => item.LocationType !== "Virtual") || [];
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getRecentlyAdded({ libraryid, limit = 20, userid }) {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      if (!userid || userid == null) {
        let adminid = await new configClass().getPreferedAdmin();
        if (!adminid || adminid == null) {
          const admins = await this.getAdmins();
          if (admins.length > 0) {
            userid = admins[0].Id;
          }
        } else {
          userid = adminid;
        }
      }

      if (!userid || userid == null) {
        console.log("[JELLYFIN-API]: getRecentlyAdded - No Admins/UserIds found");
        return [];
      }

      let url = `${this.config.JF_HOST}/Users/${userid}/Items/Latest?Limit=${limit}`;

      if (libraryid && libraryid != null) {
        url += `&ParentId=${libraryid}`;
      }

      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
        params: {
          fields: "MediaSources,DateCreated,Genres",
        },
      });

      const items = response?.data?.filter((item) => item.LocationType !== "Virtual") || [];

      return items;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getSessions() {
    if (!this.configReady) {
      return [];
    }
    try {
      let url = `${this.config.JF_HOST}/sessions`;

      const response = await axios
        .get(url, {
          headers: {
            "X-MediaBrowser-Token": this.config.JF_API_KEY,
          },
        })
        .then((response) => {
          if (this.sessionErrorCounter > 0) {
            console.log("[EMBY-API]: /sessions - Connection restored");
            this.sessionErrorCounter = 0;
          }
          return response;
        })
        .catch((error) => {
          if (this.sessionErrorCounter == 0) {
            this.sessionErrorCounter++;
            console.log("[EMBY-API]: /sessions - Unable to connect. Please check the URL and your network connection");
          }
          return { data: [] };
        });
      let result = response.data && Array.isArray(response.data) ? response.data : [];

      if (result.length > 0) {
        result = result.filter(
          (session) =>
            session.NowPlayingItem !== undefined &&
            session.NowPlayingItem.Type != "Trailer" &&
            session.NowPlayingItem.ProviderIds["prerolls.video"] == undefined
        );
      }
      return result;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async getInstalledPlugins() {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      let url = `${this.config.JF_HOST}/plugins`;

      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
      });
      return response.data;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  async StatsSubmitCustomQuery(query) {
    if (!this.configReady) {
      const success = await this.#fetchConfig();
      if (!success) {
        return [];
      }
    }
    try {
      let url = `${this.config.JF_HOST}/user_usage_stats/submit_custom_query`;

      const response = await axios.post(
        url,
        {
          CustomQueryString: query,
        },
        {
          headers: {
            "X-MediaBrowser-Token": this.config.JF_API_KEY,
          },
        }
      );
      return response.data.results;
    } catch (error) {
      this.#errorHandler(error);
      return [];
    }
  }

  #isValidUrl(string) {
    try {
      new URL(string);
      return true;
    } catch (err) {
      return false;
    }
  }

  async validateSettings(url, apikey) {
    let result = { isValid: false, status: 400, errorMessage: "Invalid URL", url: url, cleanedUrl: "" };
    try {
      let _url = url.replace(/\/web\/index\.html#!\/home\.html$/, "");

      _url = _url.replace(/\/$/, "");
      if (!/^https?:\/\//i.test(_url)) {
        _url = "http://" + _url;
      }

      if (!url.includes("/emby")) {
        _url = _url + "/emby";
      }

      result.cleanedUrl = _url;

      console.log(_url, this.#isValidUrl(_url));
      if (!this.#isValidUrl(_url)) {
        return result;
      }

      const validation_url = _url.replace(/\/$/, "") + "/system/configuration";

      const response = await axios.get(validation_url, {
        headers: {
          "X-MediaBrowser-Token": apikey,
        },
      });
      result.isValid = response.status == 200;
      return result;
    } catch (error) {
      this.#errorHandler(error);
      result.isValid = false;
      result.status = error?.response?.status ?? 400;
      result.errorMessage =
        error?.response != null
          ? this.#httpErrorMessageHandler(error)
          : error.code == "ENOTFOUND"
          ? "Unable to connect. Please check the URL and your network connection."
          : error.message;
      return result;
    }
  }

  async systemInfo() {
    if (!this.configReady) {
      return [];
    }
    let url = `${this.config.JF_HOST}/system/info`;
    try {
      const response = await axios.get(url, {
        headers: {
          "X-MediaBrowser-Token": this.config.JF_API_KEY,
        },
      });

      return response?.data || {};
    } catch (error) {
      this.#errorHandler(error, url);
      return {};
    }
  }
}

module.exports = EmbyAPI;
