import { NextFunction, Request, Response } from 'express';
import { requestGot } from '../utils/requestCall';
import {
  imageUrlToBase64,
  jkanimeInfo,
  monoschinosInfo,
  tioanimeInfo,
  videoServersJK,
  videoServersMonosChinos,
  videoServersTioAnime,
} from '../utils/util';
import { transformUrlServer } from '../utils/transformerUrl';
import AnimeModel, { Anime as ModelA } from '../database/models/anime.model';
import util from 'util';
import { hashStringMd5 } from '../utils/util';
import {
  animeExtraInfo,
  getAnimeVideoPromo,
  getAnimeCharacters,
  getRelatedAnimesMAL,
} from '../utils/util';
import urls from '../utils/urls';
import { redisClient } from '../database/connection';

// @ts-ignore
redisClient.get = util.promisify(redisClient.get);

/*
  AnimeController - a class to manage the schedule,
  top, all the animes, return the last episodes
  with async to return promises.
*/

interface Schedule {
  title: string;
  mal_id: number;
  image_url: any;
}

interface Anime {
  index: string;
  animeId: string;
  title: string;
  id: string;
  type: string;
}

interface Top {
  rank: string;
  title: string;
  url: string;
  image_url: string;
  type: string;
  subtype: string;
  page: number;
  score: string;
}

interface Episode {
  id: string;
  title: string;
  image: string;
  episode: number;
  servers: { id: string; url: string; direct: boolean };
}

interface Movie {
  id: string;
  title: string;
  type: string;
  page: string;
  banner: string;
  image: string;
  synopsis: string;
  status: string;
  rate: string;
  genres: string[];
  episodes: object[];
}

export default class AnimeController {
  async schedule(req: Request, res: Response, next: NextFunction) {
    const { day } = req.params;
    let data: any;

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `schedule_${hashStringMd5(day)}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      data = await requestGot(`${urls.BASE_JIKAN}schedule/${day}`, {
        parse: true,
        scrapy: false,
      });
    } catch (err) {
      return next(err);
    }

    const animeList: Schedule[] = data[day].map((item: Schedule) => ({
      title: item.title,
      malid: item.mal_id,
      image: item.image_url,
    }));

    if (animeList.length > 0) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */

        redisClient.set(
          `schedule_${hashStringMd5(day)}`,
          JSON.stringify({ day: animeList }),
        );

        /* After 24hrs expire the key. */

        redisClient.expireat(
          `schedule_${hashStringMd5(day)}`,
          parseInt(`${+new Date() / 1000}`, 10) + 7200,
        );
      }

      res.status(200).json({
        day: animeList,
      });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async top(req: Request, res: Response, next: NextFunction) {
    const { type, subtype, page } = req.params;
    let data: any;

    try {
      if (redisClient.connected) {
        let resultQueryRedis: any;

        if (subtype) {
          resultQueryRedis = await redisClient.get(
            `top_${hashStringMd5(`${type}:${subtype}:${page}`)}`,
          );
        } else {
          resultQueryRedis = await redisClient.get(
            `top_${hashStringMd5(`${type}:${page}`)}`,
          );
        }

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      if (subtype !== undefined) {
        data = await requestGot(
          `${urls.BASE_JIKAN}top/${type}/${page}/${subtype}`,
          { parse: true, scrapy: false },
        );
      } else {
        data = await requestGot(`${urls.BASE_JIKAN}top/${type}/${page}`, {
          parse: true,
          scrapy: false,
        });
      }
    } catch (err) {
      return next(err);
    }

    const top: Top[] = data.top.map((item: Top) => ({
      rank: item.rank,
      title: item.title,
      url: item.url,
      image_url: item.image_url,
      type: type,
      subtype: subtype,
      page: page,
      score: item.score,
    }));

    if (top.length > 0) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */
        if (subtype) {
          redisClient.set(
            `top_${hashStringMd5(`${type}:${subtype}:${page}`)}`,
            JSON.stringify({ top }),
          );
        } else {
          redisClient.set(
            `top_${hashStringMd5(`${type}:${page}`)}`,
            JSON.stringify({ top }),
          );
        }

        /* After 24hrs expire the key. */

        if (subtype) {
          redisClient.expireat(
            `top_${hashStringMd5(`${type}:${subtype}:${page}`)}`,
            parseInt(`${+new Date() / 1000}`, 10) + 7200,
          );
        } else {
          redisClient.expireat(
            `top_${hashStringMd5(`${type}:${page}`)}`,
            parseInt(`${+new Date() / 1000}`, 10) + 7200,
          );
        }
      }

      return res.status(200).json({ top });
    } else {
      return res.status(400).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getAllAnimes(req: Request, res: Response, next: NextFunction) {
    let data: any;

    try {
      data = await requestGot(`${urls.BASE_ANIMEFLV}api/animes/list`, {
        parse: true,
        scrapy: false,
      });
    } catch (err) {
      return next(err);
    }

    const animes: Anime[] = data.map((item: any) => ({
      index: item[0],
      animeId: item[3],
      title: item[1],
      id: item[2],
      type: item[4],
    }));

    if (animes.length > 0) {
      res.status(200).send({ animes });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getLastEpisodes(req: Request, res: Response, next: NextFunction) {
    let $: cheerio.Root;
    let episodes: Episode[] = [];
    let animeList: any[] = [];

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `lastEpisodes_${hashStringMd5('lastEpisodes')}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      $ = await requestGot(`${urls.BASE_MONOSCHINOS}`, {
        scrapy: true,
        parse: false,
      });
    } catch (err) {
      return next(err);
    }

    let getLastest: any = $!('.container .caps .container')[0];

    $!(getLastest)
      .find('.row article')
      .each((index: number, element: cheerio.Element) => {
        let el: cheerio.Cheerio = $(element);
        let title: string | undefined = el
          .find('.Title')
          .html()
          ?.split('\t')[0];
        let img: any = el.find('.Image img').attr('src');
        let type: any = el.find('.Image figure span').text();
        type = type.substring(1, type.length);
        let nEpisode: any = el.find('.dataEpi .episode').text();
        nEpisode = parseInt(nEpisode.split('\n')[1]);
        let id: any = el.find('a').attr('href');
        id = id.split('/')[4];
        id = id.split('-');
        id.splice(id.length - 2, 2);
        id = `${id.join('-')}-episodio-${nEpisode}`;

        let anime = {
          id: `ver/${id}`,
          title,
          image: img,
          episode: nEpisode,
        };

        animeList.push(anime);
      });

    for (const anime of animeList) {
      episodes.push({
        id: anime.id,
        title: anime.title,
        image: await imageUrlToBase64(anime.image),
        episode: anime.episode,
        servers: await videoServersMonosChinos(anime.id),
      });
    }

    if (episodes.length > 0) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */

        redisClient.set(
          `lastEpisodes_${hashStringMd5('lastEpisodes')}`,
          JSON.stringify({ episodes }),
        );

        /* After 24hrs expire the key. */

        redisClient.expireat(
          `lastEpisodes_${hashStringMd5('lastEpisodes')}`,
          parseInt(`${+new Date() / 1000}`, 10) + 1800,
        );
      }

      res.status(200).json({
        episodes,
      });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getContentTv(req: Request, res: Response, next: NextFunction) {
    const { type, page } = req.params;
    const url = 'tv';
    let data: any;

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `contentTv_${hashStringMd5(`${type}:${page}`)}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      data = await requestGot(
        `${urls.BASE_ANIMEFLV_JELU}${
          url.charAt(0).toUpperCase() + url.slice(1)
        }/${type}/${page}`,
        {
          parse: true,
          scrapy: false,
        },
      );
    } catch (err) {
      return next(err);
    }

    const animes: Movie[] = data[url].map((item: any) => {
      return {
        id: item.id,
        title: item.title,
        type: url,
        page: page,
        banner: item.banner,
        image: item.poster,
        synopsis: item.synopsis,
        status: item.debut,
        rate: item.rating,
        genres: item.genres.map((genre: any) => genre),
        episodes: item.episodes.map((episode: any) => episode),
      };
    });

    if (animes.length > 0) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */

        redisClient.set(
          `contentTv_${hashStringMd5(`${type}:${page}`)}`,
          JSON.stringify({ animes }),
        );

        /* After 24hrs expire the key. */

        redisClient.expireat(
          `contentTv_${hashStringMd5(`${type}:${page}`)}`,
          parseInt(`${+new Date() / 1000}`, 10) + 7200,
        );
      }

      res.status(200).json({
        animes,
      });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getContentSpecial(req: Request, res: Response, next: NextFunction) {
    const { type, page } = req.params;
    const url = 'special';
    let data: any;

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `contentSpecial_${hashStringMd5(`${type}:${page}`)}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      data = await requestGot(
        `${urls.BASE_ANIMEFLV_JELU}${
          url.charAt(0).toUpperCase() + url.slice(1)
        }/${type}/${page}`,
        {
          parse: true,
          scrapy: false,
        },
      );
    } catch (err) {
      return next(err);
    }

    const animes: Movie[] = data[url].map((item: any) => {
      return {
        id: item.id,
        title: item.title,
        type: url,
        page: page,
        banner: item.banner,
        image: item.poster,
        synopsis: item.synopsis,
        status: item.debut,
        rate: item.rating,
        genres: item.genres.map((genre: any) => genre),
        episodes: item.episodes.map((episode: any) => episode),
      };
    });

    if (animes.length > 0) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */

        redisClient.set(
          `contentSpecial_${hashStringMd5(`${type}:${page}`)}`,
          JSON.stringify({ animes }),
        );

        /* After 24hrs expire the key. */

        redisClient.expireat(
          `contentSpecial_${hashStringMd5(`${type}:${page}`)}`,
          parseInt(`${+new Date() / 1000}`, 10) + 7200,
        );
      }

      res.status(200).json({
        animes,
      });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getContentOva(req: Request, res: Response, next: NextFunction) {
    const { type, page } = req.params;
    const url = 'ova';
    let data: any;

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `contentOva_${hashStringMd5(`${type}:${page}`)}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      data = await requestGot(
        `${urls.BASE_ANIMEFLV_JELU}${
          url.charAt(0).toUpperCase() + url.slice(1)
        }/${type}/${page}`,
        {
          parse: true,
          scrapy: false,
        },
      );
    } catch (err) {
      return next(err);
    }

    const animes: Movie[] = data[url].map((item: any) => {
      return {
        id: item.id,
        title: item.title,
        type: url,
        page: page,
        banner: item.banner,
        image: item.poster,
        synopsis: item.synopsis,
        status: item.debut,
        rate: item.rating,
        genres: item.genres.map((genre: any) => genre),
        episodes: item.episodes.map((episode: any) => episode),
      };
    });

    if (animes.length > 0) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */

        redisClient.set(
          `contentOva_${hashStringMd5(`${type}:${page}`)}`,
          JSON.stringify({ animes }),
        );

        /* After 24hrs expire the key. */

        redisClient.expireat(
          `contentOva_${hashStringMd5(`${type}:${page}`)}`,
          parseInt(`${+new Date() / 1000}`, 10) + 7200,
        );
      }

      res.status(200).json({
        animes,
      });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getContentMovie(req: Request, res: Response, next: NextFunction) {
    const { type, page } = req.params;
    const url = 'movies';
    let data: any;

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `contentMovie_${hashStringMd5(`${type}:${page}`)}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      data = await requestGot(
        `${urls.BASE_ANIMEFLV_JELU}${
          url.charAt(0).toUpperCase() + url.slice(1)
        }/${type}/${page}`,
        {
          parse: true,
          scrapy: false,
        },
      );
    } catch (err) {
      return next(err);
    }

    const animes: Movie[] = data[url].map((item: any) => {
      return {
        id: item.id,
        title: item.title,
        type: url,
        page: page,
        banner: item.banner,
        image: item.poster,
        synopsis: item.synopsis,
        status: item.debut,
        rate: item.rating,
        genres: item.genres.map((genre: any) => genre),
        episodes: item.episodes.map((episode: any) => episode),
      };
    });

    if (animes.length > 0) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */

        redisClient.set(
          `contentMovie_${hashStringMd5(`${type}:${page}`)}`,
          JSON.stringify({ animes }),
        );

        /* After 24hrs expire the key. */

        redisClient.expireat(
          `contentMovie_${hashStringMd5(`${type}:${page}`)}`,
          parseInt(`${+new Date() / 1000}`, 10) + 7200,
        );
      }

      res.status(200).json({
        animes,
      });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getEpisodes(req: Request, res: Response, next: NextFunction) {
    const { title } = req.params;
    let searchAnime: ModelA | null;
    let episodes: any;

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `episodes_${hashStringMd5(title)}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      searchAnime = await AnimeModel.findOne({
        $or: [{ title: { $eq: title } }, { title: { $eq: `${title} (TV)` } }],
      });
    } catch (err) {
      return next(err);
    }

    switch (searchAnime?.source) {
      case 'jkanime':
        episodes = await jkanimeInfo(searchAnime?.id, searchAnime?.mal_id);
        break;
      case 'monoschinos':
        episodes = await monoschinosInfo(searchAnime?.id, searchAnime?.mal_id);
        break;
      case 'tioanime':
        episodes = await tioanimeInfo(searchAnime?.id, searchAnime?.mal_id);
        break;
      default:
        episodes = undefined;
        break;
    }

    if (episodes) {
      if (redisClient.connected) {
        /* Set the key in the redis cache. */

        redisClient.set(
          `episodes_${hashStringMd5(title)}`,
          JSON.stringify({ episodes }),
        );

        /* After 24hrs expire the key. */

        redisClient.expireat(
          `episodes_${hashStringMd5(title)}`,
          parseInt(`${+new Date() / 1000}`, 10) + 7200,
        );
      }

      res.status(200).json({ episodes });
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }

  async getServers(req: Request, res: Response, next: NextFunction) {
    const { id } = req.params;
    let data: any;

    try {
      if (redisClient.connected) {
        const resultQueryRedis: any = await redisClient.get(
          `servers_${hashStringMd5(id)}`,
        );

        if (resultQueryRedis) {
          const resultRedis: any = JSON.parse(resultQueryRedis);

          return res.status(200).json(resultRedis);
        }
      }

      let indicator = false;

      if (id.split('/')[0] === 'ver' && !indicator) {
        data = await videoServersTioAnime(id);

        if (!data.name) {
          indicator = true;
        }
      }

      if (id.split('/')[0] === 'ver' && !indicator) {
        data = await videoServersMonosChinos(id);

        if (!data.name) {
          console.log(data.name);
          indicator = true;
        }
      }

      if (!indicator) {
        data = undefined;

        indicator = true;

        /*
          This part is just for handling the error
          if the two above doesn't complete the operation
          does not make sense to have the getServers from
          JKAnime.
         */
      }

      if (data) {
        if (redisClient.connected) {
          /* Set the key in the redis cache. */

          redisClient.set(
            `servers_${hashStringMd5(id)}`,
            JSON.stringify({ servers: data }),
          );

          /* After 24hrs expire the key. */

          redisClient.expireat(
            `servers_${hashStringMd5(id)}`,
            parseInt(`${+new Date() / 1000}`, 10) + 7200,
          );
        }

        res.status(200).json({ servers: data });
      } else {
        res.status(500).json({ message: 'Aruppi lost in the shell' });
      }
    } catch (err) {
      return next(err);
    }
  }

  async getRandomAnime(req: Request, res: Response, next: NextFunction) {
    let animeQuery: ModelA[] | null;
    let animeResult: any;

    try {
      animeQuery = await AnimeModel.aggregate([{ $sample: { size: 1 } }]);
    } catch (err) {
      return next(err);
    }

    animeResult = {
      title: animeQuery[0].title || null,
      poster: animeQuery[0].poster || null,
      synopsis: animeQuery[0].description || null,
      type: animeQuery[0].type || null,
      rating: animeQuery[0].score || null,
      genres: animeQuery[0].genres || null,
      moreInfo: [await animeExtraInfo(animeQuery[0].mal_id)],
      promo: await getAnimeVideoPromo(animeQuery[0].mal_id),
      characters: await getAnimeCharacters(animeQuery[0].mal_id),
      related: await getRelatedAnimesMAL(animeQuery[0].mal_id),
    };

    if (animeResult) {
      res.set('Cache-Control', 'no-store');
      res.status(200).json(animeResult);
    } else {
      res.status(500).json({ message: 'Aruppi lost in the shell' });
    }
  }
}
