import {Injectable, Logger} from '@nestjs/common';
import {GuestsService} from "../../guests/guests.service";
import {SharedService} from "../../shared/shared.service";
import {InjectModel} from "@nestjs/mongoose";
import {Versus, VersusDocument} from "../../schemas/versus.schema";
import {Model} from "mongoose";
import {VersusDto} from "./versus.types";
import {CommandBus, QueryBus} from "@nestjs/cqrs";
import {IncreasePlayerScoreCommand} from "../../guests/command/increase-player-score.command";
import {IncreasePlayerWinningRateCommand} from "../commands/increase-player-winning-rate.command";
import {GetGuestPropertyQuery} from "../../guests/command/get-guest-property.handler";
import {GuestPropertyNamesConsts} from "../../Consts/guest-property-names.consts";
import {SetGuestPropertyCommand} from "../../guests/command/set-guest-property.command";
import {IVersusBeginSocketEvent, IVersusEndSocketEvent} from "../../Consts/types";
import {ClientNotificationType} from "../../socket/socket.gateway";
import {CreateNewQueueItemCommand} from "../commands/create-new-queue-item.command";
import {GameQueueTypes} from "../../schemas/game-queue.schema";

@Injectable()
export class VersusService {
  static configKeyCurrentAction = 'current_action';
  static configKeyActiveVersus = 'active_versus';
  private logger = new Logger(VersusService.name);
  constructor(
    private guestService: GuestsService,
    private sharedService: SharedService,
    private queryBus: QueryBus,
    @InjectModel(Versus.name) private versusModel: Model<VersusDocument>,
    private cmdBus: CommandBus,
  ) {
  }
  async simulateVersus() {
    const guests = (await this.guestService.findAll()).slice(0,2).map((guest) => {
      return {
        id: guest.telegramId,
        name: guest.name,
      }
    });
    if(guests.length < 2) {
      throw new Error("Can't simulate,  in db less than 2 players")
    }
    await this.beginVersus(guests[0].id, guests[1].id);
  }

  async beginVersus(player1: number, player2: number) {
    const [p1data,p2data] = await Promise.all([this.guestService.findById(player1), this.guestService.findById(player2)]);
    await this.cmdBus.execute(new CreateNewQueueItemCommand(player1, GameQueueTypes.versus));
    await this.sharedService.setConfig(VersusService.configKeyCurrentAction, JSON.stringify({
      action:'versus',
      data: {
        player1: player1,
        player2: player2,
        player1name: p1data.name,
        player2name: p2data.name,
      }
    }));
    this.sharedService.notifyAllClients<IVersusBeginSocketEvent>(ClientNotificationType.BeginVersus, {
      player1,
      player2,
      player1name: p1data.name,
      player2name: p2data.name
    });
  }

  async importVersus(data: VersusDto[]) {
    data.map(async (record) => {
      const item = new this.versusModel({
        ...record
      });
      await item.save();
    });
    return { result: true };
  }

  async validateVersusTasksAndResetIfNecessary() {
    const versus = await this.versusModel.aggregate([{ $match: { completed: false } }, { $sample: { size: 1 } }]).exec();
    if(versus.length == 0 ) {
      await this.markAllAsUncompleted();
    }
  }

  async getVersusTask() {
    await this.validateVersusTasksAndResetIfNecessary();
    const rand = await this.versusModel
      .aggregate([{ $match: { completed: false } }, { $sample: { size: 1 } }])
      .exec();
    // @TODO check who win with telegram

    const item = await this.versusModel.findOne({ _id: rand[0]._id }).exec();
    await this.sharedService.setConfig(VersusService.configKeyActiveVersus, item.id);
    return item;
  }
  async markAllAsUncompleted() {
    const versuses = await this.versusModel.find().exec();
    versuses.map(async (versus) => {
      versus.completed = false;
      await versus.save();
    });
    return { result: true };
  }

  async complete(winner: number, loser: number) {
    const activeVersus = await this.sharedService.getConfig(VersusService.configKeyActiveVersus);
    const item = await this.versusModel.findOne({ _id: activeVersus.value }).exec();
    item.completed = true;
    await item.save();
    const tasks = [];
    tasks.push(this.cmdBus.execute(new IncreasePlayerScoreCommand(winner, 1)));
    tasks.push(this.cmdBus.execute(new IncreasePlayerWinningRateCommand(winner, 20)));
    tasks.push(this.sharedService.setConfig(VersusService.configKeyCurrentAction, ''));
    let wonCount = await this.queryBus.execute(new GetGuestPropertyQuery(winner, GuestPropertyNamesConsts.VersusWonCount));
    let loseCount = await this.queryBus.execute(new GetGuestPropertyQuery(loser, GuestPropertyNamesConsts.VersusLoseCount));
    if(!wonCount) {
      wonCount = 1;
    } else {
      wonCount = +wonCount++;
    }
    if(!loseCount) {
      loseCount = 1;
    } else {
      loseCount = +loseCount++;
    }
    this.logger.verbose(`Set loseCount for ${loser} to ${loseCount}`);
    this.logger.verbose(`Set win count for ${winner} to ${wonCount}`);
    tasks.push(await this.cmdBus.execute(new SetGuestPropertyCommand(winner, GuestPropertyNamesConsts.VersusWonCount, wonCount.toString)));
    tasks.push(await this.cmdBus.execute(new SetGuestPropertyCommand(loser, GuestPropertyNamesConsts.VersusWonCount, loseCount.toString)));

    await Promise.all(tasks);
    this.sharedService.notifyAllClients<IVersusEndSocketEvent>(ClientNotificationType.EndVersus, {
      winner: winner
      }
    );
    return item;
  }

  async checkIfAnotherVersusInProgress() {
    this.logger.debug(`checkIfAnotherVersusInProgress enter`)
    const currentAction = await this.sharedService.getConfig(VersusService.configKeyCurrentAction);
    if(!currentAction) {
      return false;
    }
    return currentAction.value !== '';

  }
}
