import mongodb from '@ioc:Mongodb/Database'
import { Collection } from 'mongodb'

class MatchRepository {
  private collection: Collection

  constructor () {
    this.getCollection()
  }

  /**
   * Basic matchParams used in a lot of requests
   * @param puuid of the summoner
   */
  private matchParams (puuid: string, season?: number) {
    return {
      summoner_puuid: puuid,
      result: { $not: { $eq: 'Remake' } },
      gamemode: { $nin: [800, 810, 820, 830, 840, 850, 2000, 2010, 2020] },
      season: season ? season : { $exists: true },
    }
  }

  /**
   * Build the aggregate mongo query
   * @param puuid 
   * @param matchParams 
   * @param intermediateSteps 
   * @param groupId 
   * @param groupParams 
   * @param finalSteps 
   */
  private async aggregate (
    puuid: string,
    matchParams: object,
    intermediateSteps: any[],
    groupId: any,
    groupParams: object,
    finalSteps: any[],
    season?: number,
  ) {
    return this.collection.aggregate([
      {
        $match: {
          ...this.matchParams(puuid, season),
          ...matchParams,
        },
      },
      ...intermediateSteps,
      {
        $group: {
          _id: groupId,
          count: { $sum: 1 },
          wins: {
            $sum: {
              $cond: [{ $eq: ['$result', 'Win'] }, 1, 0],
            },
          },
          losses: {
            $sum: {
              $cond: [{ $eq: ['$result', 'Fail'] }, 1, 0],
            },
          },
          ...groupParams,
        },
      },
      ...finalSteps,
    ]).toArray()
  }

  /**
   * Get MongoDB matches collection
   */
  public async getCollection () {
    if (!this.collection) {
      this.collection = await mongodb.connection().collection('matches')
    }
  }

  /**
   * Get Summoner's statistics for the N most played champions
   * @param puuid of the summoner
   * @param limit number of champions to fetch
   * @param season
   */
  public async championStats (puuid: string, limit = 5, season?: number) {
    const groupParams = {
      champion: { $first: '$champion' },
      kills: { $sum: '$stats.kills' },
      deaths: { $sum: '$stats.deaths' },
      assists: { $sum: '$stats.assists' },
    }
    const finalSteps = [
      { $sort: { 'count': -1, 'champion.name': 1 } },
      { $limit: limit },
    ]
    return this.aggregate(puuid, {}, [], '$champion.id', groupParams, finalSteps, season)
  }

  /**
   * Get Summoner's statistics for all played champion classes
   * @param puuid of the summoner
   * @param season
   */
  public async championClassStats (puuid: string, season?: number) {
    const groupId = { '$arrayElemAt': ['$champion.roles', 0] }
    return this.aggregate(puuid, {}, [], groupId, {}, [], season)
  }

  /**
   * Get Summoner's complete statistics for the all played champs
   * @param puuid  of the summoner
   * @param queue  of the matches to fetch, if not set: get all matches
   * @param season of the matches to fetch, if not set: get all seasons
   */
  public async championCompleteStats (puuid: string, queue?: number, season?: number) {
    const matchParams = queue ? { gamemode: { $eq: Number(queue) } } : {}
    const groupParams = {
      time: { $sum: '$time' },
      gameLength: { $avg: '$time' },
      date: { $max: '$date' },
      champion: { $first: '$champion' },
      kills: { $sum: '$stats.kills' },
      deaths: { $sum: '$stats.deaths' },
      assists: { $sum: '$stats.assists' },
      minions: { $avg: '$stats.minions' },
      gold: { $avg: '$stats.gold' },
      dmgChamp: { $avg: '$stats.dmgChamp' },
      dmgTaken: { $avg: '$stats.dmgTaken' },
      kp: { $avg: '$stats.kp' },
    }
    const finalSteps = [
      { $sort: { 'count': -1, 'champion.name': 1 } },
    ]
    return this.aggregate(puuid, matchParams, [], '$champion.id', groupParams, finalSteps, season)
  }

  /**
   * Get Summoner's statistics for all played modes
   * @param puuid of the summoner
   * @param season
   */
  public async gamemodeStats (puuid: string, season?: number) {
    return this.aggregate(puuid, {}, [], '$gamemode', {}, [], season)
  }

  /**
   * Get global Summoner's statistics
   * @param puuid of the summoner
   * @param season
   */
  public async globalStats (puuid: string, season?: number) {
    const groupParams = {
      time: { $sum: '$time' },
      kills: { $sum: '$stats.kills' },
      deaths: { $sum: '$stats.deaths' },
      assists: { $sum: '$stats.assists' },
      minions: { $sum: '$stats.minions' },
      vision: { $sum: '$stats.vision' },
      kp: { $avg: '$stats.kp' },
    }
    return this.aggregate(puuid, {}, [], null, groupParams, [], season)
  }

  /**
  * Get Summoner's all records
  * @param puuid of the summoner
  * @param season of the matches to fetch, if null get all seasons
  */
  public async records (puuid: string, season?: number) {
    const records = await this.collection.aggregate([
      {
        $match: {
          ...this.matchParams(puuid, season),
        },
      },
      {
        $group: {
          _id: null,
          maxKills: { $max: '$stats.kills' },
          maxDeaths: { $max: '$stats.deaths' },
          maxAssists: { $max: '$stats.assists' },
          maxGold: { $max: '$stats.gold' },
          maxTime: { $max: '$time' },
          maxMinions: { $max: '$stats.minions' },
          maxKda: { $max: '$stats.realKda' },
          maxDmgTaken: { $max: '$stats.dmgTaken' },
          maxDmgChamp: { $max: '$stats.dmgChamp' },
          maxDmgObj: { $max: '$stats.dmgObj' },
          maxKp: { $max: '$stats.kp' },
          maxVision: { $max: '$stats.vision' },
          maxCriticalStrike: { $max: '$stats.criticalStrike' },
          maxLiving: { $max: '$stats.longestLiving' },
          maxHeal: { $max: '$stats.heal' },
          maxTowers: { $max: '$stats.towers' },
          maxKillingSpree: { $max: '$stats.killingSpree' },
          maxDouble: { $max: '$stats.doubleKills' },
          maxTriple: { $max: '$stats.tripleKills' },
          maxQuadra: { $max: '$stats.quadraKills' },
          maxPenta: { $max: '$stats.pentaKills' },
          docs: {
            '$push': {
              'champion': '$champion',
              'gameId': '$gameId',
              'kills': '$stats.kills',
              'deaths': '$stats.deaths',
              'assists': '$stats.assists',
              'gold': '$stats.gold',
              'time': '$time',
              'minions': '$stats.minions',
              'kda': '$stats.realKda',
              'dmgTaken': '$stats.dmgTaken',
              'dmgChamp': '$stats.dmgChamp',
              'dmgObj': '$stats.dmgObj',
              'kp': '$stats.kp',
              'vision': '$stats.vision',
              'criticalStrike': '$stats.criticalStrike',
              'longestLiving': '$stats.longestLiving',
              'heal': '$stats.heal',
              'towers': '$stats.towers',
              'killingSpree': '$stats.killingSpree',
              'doubleKills': '$stats.doubleKills',
              'tripleKills': '$stats.tripleKills',
              'quadraKills': '$stats.quadraKills',
              'pentaKills': '$stats.pentaKills',
              'result': '$result',
              'date': '$date',
              'gamemode': '$gamemode',
            },
          },
        },
      },
      {
        $project: {
          _id: 0,
          /* eslint-disable max-len */
          maxKills: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kills', '$maxKills'] } } }, 0] },
          maxDeaths: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.deaths', '$maxDeaths'] } } }, 0] },
          maxAssists: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.assists', '$maxAssists'] } } }, 0] },
          maxGold: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.gold', '$maxGold'] } } }, 0] },
          maxTime: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.time', '$maxTime'] } } }, 0] },
          maxMinions: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.minions', '$maxMinions'] } } }, 0] },
          maxKda: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kda', '$maxKda'] } } }, 0] },
          maxDmgTaken: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgTaken', '$maxDmgTaken'] } } }, 0] },
          maxDmgChamp: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgChamp', '$maxDmgChamp'] } } }, 0] },
          maxDmgObj: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgObj', '$maxDmgObj'] } } }, 0] },
          maxKp: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kp', '$maxKp'] } } }, 0] },
          maxVision: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.vision', '$maxVision'] } } }, 0] },
          maxCriticalStrike: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.criticalStrike', '$maxCriticalStrike'] } } }, 0] },
          maxLiving: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.longestLiving', '$maxLiving'] } } }, 0] },
          maxHeal: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.heal', '$maxHeal'] } } }, 0] },
          maxTowers: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.towers', '$maxTowers'] } } }, 0] },
          maxKillingSpree: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.killingSpree', '$maxKillingSpree'] } } }, 0] },
          maxDouble: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.doubleKills', '$maxDouble'] } } }, 0] },
          maxTriple: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.tripleKills', '$maxTriple'] } } }, 0] },
          maxQuadra: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.quadraKills', '$maxQuadra'] } } }, 0] },
          maxPenta: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.pentaKills', '$maxPenta'] } } }, 0] },
        },
      },
    ]).toArray()

    return records[0]
  }

  /**
   * Get Summoner's statistics for the 5 differnt roles
   * @param puuid of the summoner
   * @param season
   */
  public async roleStats (puuid: string, season?: number) {
    const matchParams = {
      role: { $not: { $eq: 'NONE' } },
    }
    const finalSteps = [
      {
        $project: {
          role: '$_id',
          count: '$count',
          wins: '$wins',
          losses: '$losses',
        },
      },
    ]
    return this.aggregate(puuid, matchParams, [], '$role', {}, finalSteps, season)
  }

  /**
   * Get Summoner's played seasons
   * @param puuid of the summoner
   */
  public async seasons (puuid: string) {
    return this.collection.aggregate([
      {
        $match: {
          ...this.matchParams(puuid),
        },
      },
      {
        $group: { _id: '$season' },
      },
    ]).toArray()
  }

  /**
   * Get Summoner's mates list
   * @param puuid of the summoner
   * @param season
   */
  public async mates (puuid: string, season?: number) {
    const intermediateSteps = [
      { $sort: { 'gameId': -1 } },
      { $unwind: '$allyTeam' },
    ]
    const groupParams = {
      account_id: { $first: '$account_id' },
      name: { $first: '$allyTeam.name' },
      mateId: { $first: '$allyTeam.account_id' },
    }
    const finalSteps = [
      {
        '$addFields': {
          'idEq': { '$eq': ['$mateId', '$account_id'] },
        },
      },
      {
        $match: {
          'idEq': false,
          'count': { $gte: 2 },
        },
      },
      { $sort: { 'count': -1, 'name': 1 } },
      { $limit: 15 },
    ]
    return this.aggregate(puuid, {}, intermediateSteps, '$allyTeam.account_id', groupParams, finalSteps, season)
  }
}

export default new MatchRepository()
