import {Injectable, Logger, Scope} from '@nestjs/common';
import {InjectModel} from '@nestjs/mongoose';
import {Model} from 'mongoose';
import {Question, QuestionDocument} from '../schemas/question.schema';
import {QuestionDto, QuestionDtoExcel} from './dto/question.dto';
import {GuestsService} from '../guests/guests.service';
import {SharedService} from '../shared/shared.service';
import {CommandBus, EventBus} from '@nestjs/cqrs';
import {ValidAnswerReceivedEvent} from '../game/events/valid-answer.recieved';
import {QuestionStorage, QuestionStorageDocument,} from '../schemas/question-storage.schema';
import {WrongAnswerReceivedEvent} from '../game/events/wrong-answer-received.event';
import {ProceedGameQueueCommand} from '../game/commands/proceed-game-queue.command';
import {getRandomInt} from '../helpers/rand-number';
import {Messages} from "../messaging/tg.text";
import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.command";
import {GameQueueTypes} from "../schemas/game-queue.schema";
import {IncreasePlayerWinningRateCommand} from "../game/commands/increase-player-winning-rate.command";
import {IncreasePlayerScoreCommand} from "../guests/command/increase-player-score.command";
import {FeatureflagService} from "../featureflag/featureflag.service";
import {FeatureFlagsConsts} from "../Consts/FeatureFlags.consts";
import {QuizEndGameResults} from "./quiz.types";
import {ClientNotificationType} from "../socket/socket.gateway";
import {BeginVersusCommand} from "../game/commands/begin-versus.command";

@Injectable({ scope: Scope.TRANSIENT })
export class QuizService {
  private readonly logger = new Logger(QuizService.name);
  constructor(
    @InjectModel(Question.name) private questionModel: Model<QuestionDocument>,
    @InjectModel(QuestionStorage.name)
    private questionStorageModel: Model<QuestionStorageDocument>,
    private guestService: GuestsService,
    private sharedService: SharedService,
    private eventBus: EventBus,
    private commandBus: CommandBus,
    private featureFlagService: FeatureflagService,
  ) {

  }

  async get(): Promise<QuestionDocument> {
    return this.questionModel.find().sort({ _id: -1 }).findOne();
  }

  async setQuestion(questionDto: QuestionDto, target: number = null) {
    await this.sharedService.setConfig('currentQuestion', questionDto.id)
    const item = new this.questionModel(questionDto);
    await item.save();
    this.logger.verbose(`Question updated`);
    await this.guestService.postQuestion(questionDto, target);
    this.sharedService.notifyAllClients<QuestionDto>(ClientNotificationType.QuestionChanged, questionDto);
    return item.save();
  }

  async validateAnswerInline(answer:string, id: number) {
    this.logger.verbose(`[validateAnswer] enter ${answer} ${id}`);
    const question = await this.get();
    this.logger.verbose(
      `Validating answer for question: ${JSON.stringify(question.text)}`,
    );
    // check that answer exist
    const shortAnswers = question.answers.map((answer) => answer.substring(0,50));
    if(question.countdownFinished) {
      return;
    }
    const shortValidAnswer = question.valid.substring(0,50);
    if(shortAnswers.indexOf(answer) === -1) {
      this.logger.warn(`[validateAnswer] this question is not on game now`);
      return;
    }
    const isAnswerValid = shortValidAnswer === answer;
    if(question.userAnswers.find(answer => answer.user === id)) {
      this.logger.verbose("question->user answer is already containing record");
      return;
    }
    question.userAnswers.push({
      user: id,
      valid: isAnswerValid,
      time: new Date()
    })
    await question.save();
    this.logger.verbose("question saved with user details")
    if (shortValidAnswer=== answer) {
      question.answeredBy = id;
      this.logger.verbose(`extra ${question.note}`);
      this.eventBus.publish(
        new ValidAnswerReceivedEvent(id, answer, question.note),
      );
      await question.save();
      await this.markQuestionStorageAsAnsweredCorrectly(question.text);
      return true;
    }
    this.eventBus.publish(new WrongAnswerReceivedEvent(id, question.valid));

    return false;
  }

  async markQuestionStorageAsAnsweredCorrectly(questionText: string) {
    this.logger.verbose(
      `Marking question ${questionText} is answered correctly`,
    );
    const question = await this.questionStorageModel
      .findOne({ text: questionText })
      .exec();
    question.isAnsweredCorrectly = true;
    await question.save();
  }

  async populateQuestions(questionDto: QuestionDto[]) {
    for (const item of questionDto) {
      const newItem = new this.questionStorageModel({
        ...item,
      });
      await newItem.save();
    }
  }

  async proceedWithGame() {
    this.logger.verbose(`[proceedWithGame] Executing proceed with game`);
    await this.calculateScore();
    await this.commandBus.execute(new ProceedGameQueueCommand());
    return Promise.resolve(true);
  }

  private checkIfWeShouldStartVersus(answers: { valid: boolean; time: number; user: number }[]) {
    if(answers.length === 0 && answers.length <= 2) {
      return false;
    }
    const diff = Math.abs(new Date(answers[0].time).getTime() - new Date(answers[1].time).getTime()) / 1000;
    return diff <= 0.5;
  }

  async calculateScore() {
    const question = await this.get();
    if(question.scoreCalculated) {
      return;
    }
    if(!await this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.DontMarkQuestionsAsCompleted)) {
      this.logger.verbose(`[proceedWithGame]: DontMarkQuestionsAsCompleted disabled, marking as complete`);
      question.answered = true;
    }
    this.logger.verbose(`[calculateScore] enter `);
    const playerAnswers = question.userAnswers.map((answer) => {
      return {
        user: answer.user,
        valid: answer.valid,
        time: answer.time.getTime()
      }
    });
    const sortedAnswers = playerAnswers.sort((a, b) =>  a.time - b.time);
    const winner = sortedAnswers.find((answer) => answer.valid);
    let targetUser = 0;
    if(winner) {
      const totalWinningScore = getRandomInt(40,60);
      sortedAnswers.filter(x => x.valid).forEach((answer) => {
        this.logger.debug(`Giving 1 point to all who answered right`);
        this.commandBus.execute(new IncreasePlayerWinningRateCommand(answer.user,
          totalWinningScore / sortedAnswers.filter((answer) => answer.valid).length));
        this.commandBus.execute(new IncreasePlayerScoreCommand(answer.user,1));
      });
      const ffState = await this.featureFlagService.getFeatureFlag(FeatureFlagsConsts.StartVersusIfPlayersAnsweredInSameTime)
      if(ffState.state) {
        if(this.checkIfWeShouldStartVersus(sortedAnswers.filter(x => x.valid))) {
          await this.commandBus.execute(
            new BeginVersusCommand(
              sortedAnswers.filter(x => x.valid)[0].user,
              sortedAnswers.filter(x => x.valid)[1].user,
              ));
        }
      }
      await this.commandBus.execute(new IncreasePlayerWinningRateCommand(sortedAnswers[0].user, getRandomInt(4,9)));
      this.logger.debug(`Giving 1 point to first`);
      await this.commandBus.execute(new IncreasePlayerScoreCommand(winner.user,1));
      targetUser = winner.user;
    }

    const invalidAnswers = sortedAnswers.filter((answer) => !answer.valid)
    if(invalidAnswers.length > 0) {
      //const lastInvalidAnswer = invalidAnswers[invalidAnswers.length - 1];
      const lastInvalidAnswer = invalidAnswers.sort((a,b) => a.time - b.time)[0];
      if(!lastInvalidAnswer) {
        return;
      }
      const random = getRandomInt(0,100);
      if(random > 50) {
        await this.guestService.updatePenaltiesCount(lastInvalidAnswer.user);
        await this.commandBus.execute(new CreateNewQueueItemCommand(lastInvalidAnswer.user, GameQueueTypes.penalty));
        targetUser = lastInvalidAnswer.user;
      }
    }
    await this.commandBus.execute(new CreateNewQueueItemCommand(targetUser, GameQueueTypes.showresults));
    question.scoreCalculated = true;
    await question.save();
  }

  public async calculateEndgamePoints(): Promise<QuizEndGameResults> {
    const maxInvalidAnswersPromise =  this.guestService.getModel().find({}).sort({ ['invalidAnswers']: 'desc'}).exec();
    const maxRewardsPromise =  this.guestService.getModel().find({}).sort({['rewardsReceived']: "desc"}).exec();
    const maxPenaltiesPromise = this.guestService.getModel().find({}).sort({['penaltiesReceived']: 'desc'}).exec();

    const [maxRewards, maxInvalidAnswers, maxPenaltiesReceived] = await  Promise.all([maxRewardsPromise, maxInvalidAnswersPromise, maxPenaltiesPromise]);
    const result =  {
      maxInvalidAnswers: {
        id: maxInvalidAnswers[0].telegramId,
        count: maxInvalidAnswers[0].invalidAnswers,
        name: maxInvalidAnswers[0].name,
      },
      maxRewards: {
        id: maxRewards[0].telegramId,
        count: maxRewards[0].rewardsReceived,
        name: maxRewards[0].name,
      },
      maxPenalties: {
        id: maxPenaltiesReceived[0].telegramId,
        count: maxPenaltiesReceived[0].penaltiesReceived,
        name: maxPenaltiesReceived[0].name,
      }
    }
    await this.sharedService.setConfig('endgame-points', JSON.stringify(result));
    await this.commandBus.execute(new IncreasePlayerScoreCommand(result.maxInvalidAnswers.id, 2));
    await this.commandBus.execute(new IncreasePlayerScoreCommand(result.maxPenalties.id, 2));
    await this.commandBus.execute(new IncreasePlayerScoreCommand(result.maxRewards.id, -2));
    return result;
  }

  private async getNextQuestion() {
    let question = await this.questionStorageModel
      .findOne({ isAnswered: false })
      .exec();
    if (!question) {
      const unanswered = await this.getRemainQuestionWithoutValidAnswer();
      const skipRand = getRandomInt(0, unanswered);
      question = await this.questionStorageModel
        .findOne({ isAnsweredCorrectly: false })
        .skip(skipRand)
        .exec();
    }
    return question;
  }

  async playNextQuestion() {
    this.logger.verbose(`Playing next question`);
    const question = await this.getNextQuestion();
    question.isAnswered = true;
    await this.setQuestion({
      qId: question.id,
      id: question.id,
      text: question.text,
      answers: question.answers,
      valid: question.valid,
      note: question.note ? question.note : null,
    });
    await question.save();
  }

  async playExtraQuestion(telegramId: number) {
    const question = await this.getNextQuestion();
    this.logger.verbose(`playExtraQuestion: ${question.text}`);
    await this.setQuestion(
      { qId: question.id, id: question.id, text: question.text, answers: question.answers, valid: question.valid, note: question.note },
      telegramId,
    );
    question.isAnswered = true;
    await question.save();
    return question;
  }

  async markQuestionAsUnread() {
    this.logger.verbose(`markQuestionAsUnread: enter`);
    await this.questionStorageModel
      .updateMany({}, { isAnswered: false })
      .exec();
    this.logger.verbose(`markQuestionAsUnread: done`);
  }

  async getRemainQuestionCount(): Promise<number> {
    const questions = await this.questionStorageModel
      .find({ isAnswered: false })
      .exec();
    return questions.length;
  }

  async getRemainQuestionWithoutValidAnswer(): Promise<number> {
    const questions = await this.questionStorageModel.find({
      isAnsweredCorrectly: false,
    });
    return questions.length;
  }

  async importQuestions(questions: QuestionDto[]) {
    await this.questionStorageModel.deleteMany({}).exec();
    for (const item of questions) {
      const newQuestion = new this.questionStorageModel({
        text: item.text,
        valid: item.valid,
        answers: item.answers,
        isAnswered: false,
      });
      await newQuestion.save();
    }
    return true;
  }

  dealPrize() {
    return Promise.resolve(undefined);
  }

  async importExcel(questions: QuestionDtoExcel[]) {
    for (const item of questions) {
      this.logger.verbose(`importExcel: importing: ${item}`);
      const answers = [];
      answers.push(item.a1);
      answers.push(item.a2);
      answers.push(item.a3);
      answers.push(item.a4);
      const newQuestion = new this.questionStorageModel({
        text: item.text,
        isAnswered: false,
        answers: answers,
        valid: item.valid,
        note: item.note ? item.note : null,
      });
      await newQuestion.save();
    }
  }

  async getQuestionResults() {
    const question = await this.get();
    return question.userAnswers;
  }

  async displayQuestionForUser(telegramId: number) {
    const question = await this.get();
    const dto: QuestionDto = {
      id: question.id,
      text: question.text,
      answers: question.answers,
      valid: question.valid,
      note: question.note,
      qId: question.qId,
    }
    await this.guestService.postQuestion(dto, telegramId);
  }

  async getEndgameResults() {
    const res = await this.sharedService.getConfig('endgame-points');
    return JSON.parse(res.value);
  }

  async questionTimeout() {
    const question = await this.get();
    question.countdownFinished = true;
    await  question.save();
    return question;
  }
}
