import SkinModel from "../../data/SkinModel";
import * as Skins from "../../data/skins";
import { knex } from "../../db";
import SkinResolver from "./resolvers/SkinResolver";
import LRU from "lru-cache";
import { Int } from "grats";
import { ISkin } from "./resolvers/CommonSkinResolver";
import UserContext from "../../data/UserContext.js";

const options = {
  max: 100,
  maxAge: 1000 * 60 * 60,
};
let skinCount: number | null = null;
const cache = new LRU<string, Skins.MuseumPage>(options);

// A supery hacky global cache for common requests.
async function getMuseumSkinCountFromCache() {
  if (skinCount == null) {
    skinCount = await Skins.getClassicSkinCount();
  }
  return skinCount;
}

// A supery hacky global cache for common requests.
async function getSkinMuseumPageFromCache(first: number, offset: number) {
  const key = `${first}-${offset}`;
  const cached = cache.get(key);
  if (cached != null) {
    return cached;
  }
  const skins = await Skins.getMuseumPage({
    offset: Number(offset),
    first: Number(first),
  });
  cache.set(key, skins);
  return skins;
}

/**
 * A collection of classic Winamp skins
 * @gqlType
 */
export default class SkinsConnection {
  _first: number;
  _offset: number;
  _sort?: string | null;
  _filter?: string | null;
  constructor(
    first: number,
    offset: number,
    sort?: string | null,
    filter?: string | null
  ) {
    this._first = first;
    this._offset = offset;
    this._filter = filter;
    this._sort = sort;
  }
  _getQuery() {
    let query = knex("skins").where({ skin_type: 1 });

    // TODO: What happens if there are multiple tweets/reviews?
    // Do we return multiple instances of that skin?
    switch (this._filter) {
      case "APPROVED":
        query = query
          .leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5")
          .where("review", "APPROVED");
        break;
      case "REJECTED":
        query = query
          .leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5")
          .where("review", "REJECTED");
        break;
      case "NSFW":
        query = query
          .leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5")
          .where("review", "NSFW");
        break;
      case "TWEETED":
        query = query.leftJoin("tweets", "tweet.skin_md5", "=", "skins.md5");
    }

    return query;
  }

  /**
   * The total number of skins matching the filter
   * @gqlField
   */
  async count(): Promise<Int> {
    if (this._sort === "MUSEUM") {
      // This is the common case, so serve it from cache.
      return getMuseumSkinCountFromCache();
    }
    const count = await this._getQuery().count("*", { as: "count" });
    return Number(count[0].count);
  }

  /**
   * The list of skins
   * @gqlField
   */
  async nodes(ctx: UserContext): Promise<Array<ISkin | null>> {
    if (this._sort === "MUSEUM") {
      if (this._filter) {
        throw new Error(
          "We don't support combining sorting and filtering at the same time."
        );
      }
      const items = await getSkinMuseumPageFromCache(this._first, this._offset);
      return Promise.all(
        items.map(async (item) => {
          const model = await SkinModel.fromMd5Assert(ctx, item.md5);
          return SkinResolver.fromModel(model);
        })
      );
    }

    const skins = await this._getQuery()
      .select()
      .limit(this._first)
      .offset(this._offset);
    return skins.map((skin) => {
      return SkinResolver.fromModel(new SkinModel(ctx, skin));
    });
  }
}

/** @gqlEnum */
type SkinsSortOption =
  /**
the Museum's (https://skins.webamp.org) special sorting rules.

Roughly speaking, it's:

1. The four classic default skins
2. Tweeted skins first (sorted by the number of likes/retweets)
3. Approved, but not tweeted yet, skins
4. Unreviwed skins
5. Rejected skins
6. NSFW skins
*/
  "MUSEUM";

/** @gqlEnum */
type SkinsFilterOption =
  /*
Only the skins that have been approved for tweeting
*/
  | "APPROVED"

  /*
Only the skins that have been rejected for tweeting
*/
  | "REJECTED"

  /*
Only the skins that have been marked NSFW
*/
  | "NSFW"

  /*
Only the skins that have been tweeted
*/
  | "TWEETED";

/**
 * All classic skins in the database
 *
 * **Note:** We don't currently support combining sorting and filtering.
 * @gqlQueryField */
export function skins({
  first = 10,
  offset = 0,
  sort,
  filter,
}: {
  first?: Int;
  offset?: Int;
  sort?: SkinsSortOption | null;
  filter?: SkinsFilterOption | null;
}): SkinsConnection {
  if (first > 1000) {
    throw new Error("Maximum limit is 1000");
  }
  return new SkinsConnection(first, offset, sort, filter);
}
