import { ApolloError, UserInputError } from 'apollo-server';
import { PlatformId } from '../../../database/types/members';
import { Channels, memcache } from '../../modules';
import { ChannelId } from '../../modules/types/youtube';
import { cutChannelIds, cutGroupString, escapeRegex, firstField, getCacheKey, getNextToken, parseOrganization, parseToken, Sort } from './consts';

const CACHE_TTL = +(process.env.TTL_LONG ?? 900);

interface OrderBy {
  _id?: Sort;
  published_at?: Sort;
  subscribers?: Sort;
}

interface ChannelsQuery {
  _id: number[];
  name: string;
  organizations: string[];
  exclude_organizations: string[];
  platforms: PlatformId[];
  exclude_channel_id: ChannelId[];
  channel_id: ChannelId[];
  order_by: OrderBy;
  page_token: string;
  limit: number;
}

export async function channels(_, query: ChannelsQuery) {
  try {
    const {
      _id = [],
      name = '',
      organizations = [],
      exclude_organizations = [],
      exclude_channel_id = [],
      channel_id = [],
      platforms = [],
      page_token = '',
      limit
    } = query;
    if (limit < 1 || limit > 50) {
      return new UserInputError('limit must be between 1-50 inclusive.');
    }
    if (organizations.length && exclude_organizations.length) {
      return new UserInputError('Setting both organizations and exclude_organizations is redundant. Only choose one.');
    }
    if (channel_id.length && exclude_channel_id.length) {
      return new UserInputError('Setting both channel_id and exclude_channel_id is redundant. Only choose one.');
    }
    const EXCLUDE_ORG = !organizations.length;
    const EXCLUDE_IDS = !channel_id.length;
    const [ORDER_BY, ORDER_BY_KEY] = firstField(query.order_by);
    const [ORDER_KEY, ORDER_VALUE] = Object.entries(ORDER_BY)[0];
    const sortById = ORDER_KEY === '_id';
    const sortBy = sortById ? ORDER_BY : { [`channel_stats.${ORDER_KEY}`]: ORDER_VALUE };
    const ORGANIZATIONS = parseOrganization(EXCLUDE_ORG ? exclude_organizations : organizations);
    const CHANNEL_IDS = EXCLUDE_IDS ? exclude_channel_id : channel_id;
    const CACHE_KEY = getCacheKey(`CHNLS:${+EXCLUDE_ORG}${+EXCLUDE_IDS}${_id}${(name)}${cutGroupString(ORGANIZATIONS)}${cutChannelIds(CHANNEL_IDS)}${platforms}${limit}${ORDER_BY_KEY}${page_token}`, false);

    const cached = await memcache.get(CACHE_KEY);
    if (cached) return cached;

    const QUERY = {
      _id: { [_id[0] ? '$in' : '$nin']: _id },
      ...name && { $or: getNameQueries(name) },
      ...ORGANIZATIONS[0] && { organization: {
        ...EXCLUDE_ORG
          ? { $not: { $regex: ORGANIZATIONS, $options: 'i' } }
          : { $regex: ORGANIZATIONS, $options: 'i' }
      } },
      ...channel_id[0] && { channel_id: { [EXCLUDE_IDS ? '$nin' : '$in']: CHANNEL_IDS } },
      ...platforms[0] && { platform_id: { $in: platforms } }
    };

    const getChannelCount = Channels.countDocuments(QUERY);
    const getUncachedChannels = Channels
      .find({
        ...QUERY,
        ...page_token && { [Object.keys(sortBy)[0]]: { [ORDER_VALUE === 'asc' ? '$gte' : '$lte']: parseToken(page_token) } },
      })
      .sort(sortBy)
      .limit(limit + 1)
      .lean()
      .exec();

    const [channelCount, uncachedChannels] = await Promise.all([getChannelCount, getUncachedChannels]);
    const results = {
      items: uncachedChannels,
      next_page_token: null,
      page_info: {
        total_results: channelCount,
        results_per_page: limit
      }
    };

    const hasNextPage = uncachedChannels.length > limit && results.items.pop();
    if (hasNextPage) {
      const token = sortById ? hasNextPage._id : hasNextPage.channel_stats[ORDER_KEY];
      results.next_page_token = getNextToken(token);
    }

    memcache.set(CACHE_KEY, results, CACHE_TTL);
    return results;
  } catch(err) {
    throw new ApolloError(err);
  }
}

const getNameQueries = (name: string) => {
  const nameRegex = escapeRegex(unescape(name)).split(/ +/g).map(string => `(?=.*${string})`).join('');
  return [
    { 'name.en': { $regex: nameRegex, $options: 'i' } },
    { 'name.jp': { $regex: nameRegex, $options: 'i' } },
    { 'name.kr': { $regex: nameRegex, $options: 'i' } },
    { 'name.cn': { $regex: nameRegex, $options: 'i' } }
  ];
};