import {Inject, Injectable, Logger} from '@nestjs/common';
import {InjectModel} from '@nestjs/mongoose';
import {Guest, GuestDocument} from '../schemas/guest.schema';
import {Document, Model} from 'mongoose';
import {CreateGuestDto} from './dto/create-guest.dto';
import {QuestionDto} from '../quiz/dto/question.dto';
import {Messages} from '../messaging/tg.text';
import {ExtraReplyMessage} from 'telegraf/typings/telegram-types';
import {CommandBus, EventBus, QueryBus} from '@nestjs/cqrs';
import {UserRegisteredEvent} from '../game/events/user-registered.event';
import {DealCardsCommand} from '../game/commands/deal-cards.command';
import {GameCard} from '../game/entities/cards.entities';
import {CardsService} from '../cards/cards.service';
import {QuizAnswerStateEnum} from '../game/entities/quiz-answer-state.enum';
import {PostCardsToUserCommand} from '../game/commands/post-cards-to-user.command';
import {CardSelectionPendingEvent} from '../game/events/card-selection-pending.event';
import {ScoreChangedEvent} from '../game/events/score-changed.event';
import {PrizeChangeIncreasedEvent} from '../game/events/prize-change-increased.event';
import {Config, ConfigDocument} from '../schemas/config.schema';
import {ClientProxy} from "@nestjs/microservices";
import {CommandsConsts} from "../Consts/commands.consts";
import {ChatMessageRequestModel} from "../messaging/models/chat-message-request.model";
import {MqtMessageModel} from "../messaging/models/mqt-message.model";
import {ConfigService} from "@nestjs/config";
import {StringHelper} from "../helpers/stringhelper";
import {DebuffsConsts} from "../game/entities/debuffs.consts";
import {VoiceService} from "../voice/voice.service";
import {screpaDictManyInvalidAnswersDict} from "../voice/dicts/screpa-dict-many-invalid-answers.dict";
import {GuestPropertiesConsts} from "../schemas/properties.consts";
import {GuestNamesInCases} from "./guest.types";
import {QuizService} from "../quiz/quiz.service";

@Injectable()
export class GuestsService {
  readonly nums = Messages.answerNumbers;
  private readonly logger = new Logger(GuestsService.name);
  constructor(
    @InjectModel(Guest.name) private guestModel: Model<GuestDocument>,
    @InjectModel(Config.name) private configModel: Model<ConfigDocument>,
    @Inject('Telegram') private telegramService: ClientProxy,
    private eventBus: EventBus,
    private cmdBus: CommandBus,
    private cardServices: CardsService,
    private configService: ConfigService,
    private commandBus: CommandBus,
    private queryBus: QueryBus,
    private voiceService: VoiceService,
  ) {}

  async create(createGuestDto: CreateGuestDto): Promise<Guest> {
    const guest = await this.guestModel
      .findOne({ telegramId: createGuestDto.telegramId })
      .exec();
    if (guest) {
      return;
    }
    const newGuest = new this.guestModel(createGuestDto);
    const res = await newGuest.save();
    this.eventBus.publish(
      new UserRegisteredEvent(createGuestDto.telegramId, createGuestDto.name),
    );
    return res;
  }

  async findAll(){
    return this.guestModel.find().exec();
  }

  getModel() {
    return this.guestModel;
  }

  async filter(properties: object) {
    return this.guestModel.find(properties).exec();
  }

  async findById(id: number) {
    const result = await this.guestModel.findOne({ telegramId: id }).exec();
    if(!result) {
      return null;
    }
    delete result.photo;
    return result;
  }

  async hideKeyboard(text: string) {
    this.logger.verbose(`Hide keyboard`);
    (await this.findAll()).forEach((guest) => {
      this.telegramService.emit<any, ChatMessageRequestModel>({ cmd:  CommandsConsts.SendMessage}, {chatId: guest.chatId, message: text,extra: { reply_markup: {
               remove_keyboard: true,
             }, }});
    });
    this.logger.verbose(`Keyboard hidden`);
  }




  async postQuestion(questionDto: QuestionDto, targetId = null) {
    const guests = await this.findAll();
    const extra = {
      reply_markup: {
        keyboard: [],
        inline_keyboard: [],
      },
    };
    questionDto.answers.forEach((item, index) => {
      // extra.reply_markup.keyboard.push([
      //   { text: this.nums[index] + ' ' + item },
      // ]);
      if(item !== null){
        extra.reply_markup.inline_keyboard.push(
          [ { text: item, callback_data: `${item.substring(0,50)}` },
          ]);
      }
    });
    if (!targetId) {
      guests.forEach((guest) => {
        if(parseInt(guest.get(StringHelper.getPropertyName(DebuffsConsts.bannedFor))) > 0) {
          const bannedVal = parseInt(guest.get(StringHelper.getPropertyName(DebuffsConsts.bannedFor)));
          this.logger.warn(`Skipping ${guest.id} since it is banned, remaining  (${bannedVal})`);
          return;
        }
        this.telegramService.emit<MqtMessageModel,ChatMessageRequestModel>(
          { cmd: CommandsConsts.SendMessage},
          {
            chatId: guest.chatId,
            message:`Внимание! Вопрос!\r\n${questionDto.text}\r\n\r\nВыбери ответ:`,
            extra
          })
      });
    } else {
      const user = guests.find((x) => x.telegramId === targetId);
      if (user) {
        this.telegramService.emit<MqtMessageModel, ChatMessageRequestModel>({ cmd: CommandsConsts.SendMessage}, {
          chatId: user.chatId,
          message: `Внимание! Вопрос!\r\n${questionDto.text}\r\n\r\nВыбери ответ:`,
          extra
        });
      }
    }
  }

  async setPhoto(id: number, file: string) {
    const user = await this.findById(id);
    if (!user) {
      return;
    }
    user.photo = file;
    await user.save();
  }

  async getPhoto(id: number) {
    const image = await this.guestModel.findOne({ telegramId: id }).exec();
    return image.photo;
  }

  private async broadcast(message: string, extra: ExtraReplyMessage) {
    (await this.findAll()).forEach((guest) => {
      this.telegramService.emit({ cmd: CommandsConsts.SendMessage}, {
        chatId: guest.chatId,
        message,
        extra
      })
     });
  }

  async notifyAboutValidAnswer(tId: number) {
    const winner = await this.findById(tId);
    const message = `Правильный ответ предоставил: ${winner.name}`;
    await this.broadcast(message, {});
  }

  async dealCards() {
    const guests = await this.findAll();
    guests.forEach((guest) => {
      this.cmdBus.execute(new DealCardsCommand(guest.telegramId));
    });
  }

  private async getCards(guest: number, state: QuizAnswerStateEnum) {
    const buttons = (
      await this.cardServices.getCardsForState(
        guest,
        state,
      )
    ).map((card) => card.emoji + ' ' + card.cardName);
    this.logger.log(`cards for ${guest} is ${JSON.stringify(buttons)}`);
    return buttons;
  }

  async sendButtonsToPlayer(user: number, state: QuizAnswerStateEnum, inline: boolean) {
    const cards = await this.getCards(user, state);
    const userDocument = await this.findById(user);
    await this.cmdBus.execute(
      new PostCardsToUserCommand(user, userDocument.chatId, cards, inline),
    );
  }

  async sendButtonsToPlayers(
    playerId: number,
    playerState: QuizAnswerStateEnum,
    othersPlayersState: QuizAnswerStateEnum,
  ) {
    const guests = await this.findAll();
    for (const guest of guests) {
      if (!isNaN(playerId) && guest.telegramId === playerId) {
        const buttons = await this.getCards(guest.telegramId, playerState);
        await this.cmdBus.execute(
          new PostCardsToUserCommand(guest.telegramId, guest.chatId, buttons),
        );
      } else {
        const buttons = await this.getCards(
          guest.telegramId,
          othersPlayersState,
        );
        await this.cmdBus.execute(
          new PostCardsToUserCommand(guest.telegramId, guest.chatId, buttons),
        );
      }
    }
    await this.eventBus.publish(new CardSelectionPendingEvent());
  }

  async sendValidAnswerActions(validAnsweredId: number) {
    await this.sendButtonsToPlayers(
      validAnsweredId,
      QuizAnswerStateEnum.playerAnsweredCorrectly,
      QuizAnswerStateEnum.someoneAnsweredCorrectly,
    );
  }

  async sendActionsBetweenRounds(user: number, inline: boolean) {
    //await  this.sendButtonsToPlayers(user, QuizAnswerStateEnum.betweenRounds, QuizAnswerStateEnum.betweenRounds);
    this.logger.verbose(`sendActionBetweenRounds ${user}, inline: ${inline}`);
    await this.sendButtonsToPlayer(user, QuizAnswerStateEnum.betweenRounds, inline);
  }

  async sendRewardActions(telegramId: number) {
    await this.sendButtonsToPlayers(
      telegramId,
      QuizAnswerStateEnum.playerGettingReward,
      QuizAnswerStateEnum.someoneGettingReward,
    );
  }

  async sendPenaltyActions(telegramId: number) {
    this.logger.verbose(`Send penalty actions`);
    await this.sendButtonsToPlayers(
      telegramId,
      QuizAnswerStateEnum.playerGotPenalty,
      QuizAnswerStateEnum.someoneGettingPenalty,
    );
  }


  async sendDealedCards(telegramId: number, cards: GameCard[]) {
    const user = await this.findById(telegramId);
    const cardsStr = cards
      .map((card) => {
        return card.getEmoji() + ' ' + card.description;
      })
      .join('\r\n');
    this.telegramService.emit({ cmd: 'SendMessage' }, { chatId: user.chatId, message: `Вы получили следующие карты: \r\n${cardsStr}`});

  }

  async resetPlayersScore() {
    this.logger.verbose(`Resetting player scores`);
    return this.guestModel.updateMany({}, { score: 0 }).exec();
  }

  public async getCoefficient(): Promise<number> {
    let result = 1;
    const configEntity = await this.configModel
      .findOne({ key: 'PrizeCoefficient' })
      .exec();
    if (configEntity) {
      this.logger.log(`getCoefficient() exist ${configEntity.value}`);
      result = +configEntity.value;
    }
    this.logger.log(`getCoefficient(): ${result}`);
    return result;
  }

  async updatePlayerScore(tId: number, number: number) {
    this.logger.verbose(`Changing score for player ${tId}, changes ${number}`);
    const player = await this.guestModel.findOne({ telegramId: tId }).exec();
    player.score = player.score + number;
    player.validAnswers = player.validAnswers++;
    this.logger.verbose(`New score for ${tId} is ${player.score}`);
    this.eventBus.publish(new ScoreChangedEvent(tId, player.score));
    await player.save();
  }

  async changeWinningChance(tId: number, number: number) {
    this.logger.verbose(`Changing winning chance for player ${tId} by ${number}`);
    const player = await this.findById(tId);
    player.prizeChance = player.prizeChance + number;
    await player.save();
    this.eventBus.publish(
      new PrizeChangeIncreasedEvent(tId, player.prizeChance),
    );
  }

  async

  async giveCards() {
    const cardsOnHand = this.configService.get<number>('CARDS_ON_HAND');
    const guests = await this.findAll();
    for (const guest of guests) {
      const cards = await this.cardServices.getCards(guest.telegramId);

      if (cards.length < cardsOnHand) {
        for (let i = cards.length; i < cardsOnHand; i++) {
          const newcard = await this.cardServices.withdrawNewCard(guest.telegramId);
          await this.sendDealedCards(guest.telegramId, [newcard]);
        }
      }
    }
  }

  async sendQuestionToUser(telegramId: number, question: QuestionDto) {
    await this.postQuestion(question, telegramId);
  }

  async resetWinningChance() {
    await this.guestModel.updateMany({}, { prizeChance: 0 });
  }

  async getGuestNameInCases(telegramId: number): Promise<GuestNamesInCases> {
    const guest = await this.findById(telegramId);
    return {
      SubjectiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameSubjectiveCase)),
      AccusativeCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameAccusativeCase)),
      GenitiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameGenitiveCase))
    }
  }

  async incrementInvalidAnswersCount(tId: number) {
    this.logger.verbose(`Increment invalid answers in the row for ${tId}`);
    const guest = await this.findById(tId);
    if(!guest) {
      this.logger.error(`Can't find user ${tId} for incrementing invalid answers count`);
      return;
    }
    guest.invalidAnswers = +guest.invalidAnswers + 1;
    guest.invalidAnswersInRow = +guest.invalidAnswersInRow + 1;
    this.logger.verbose(`Invalid answers: ${guest.invalidAnswers}, inRow: ${guest.invalidAnswersInRow}`);
    if(guest.invalidAnswersInRow >= 3) {
      const text = this.voiceService.buildTemplate({ text: '', vars: '', voice: 2 },{
        SubjectiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameSubjectiveCase)),
        AccusativeCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameAccusativeCase)),
        GenitiveCase: guest.get(StringHelper.getPropertyName(GuestPropertiesConsts.NameGenitiveCase))
      }, [...screpaDictManyInvalidAnswersDict])
      //await this.commandBus.execute(new CreateNewQueueItemCommand(guest.telegramId, GameQueueTypes.screpaAnounce, text));
      // await this.commandBus.execute(new CreateNewQueueItemCommand(guest.telegramId, GameQueueTypes.penalty));
      this.logger.verbose(`Reset invalidAnswerInRow, since user received penalty`);
      guest.invalidAnswersInRow = 0;
    }
    await guest.save();
  }

  async resetInvalidAnswersInTheRow(tId: number) {
    const guest = await this.findById(tId);

    guest.invalidAnswersInRow = 0;
    await guest.save();
  }

  async incrementPrizeCount(telegramId: number) {
    const guest = await this.findById(telegramId);
    guest.rewardsReceived += 1;
    await guest.save();
  }

  async updatePenaltiesCount(user: number) {
    const guest = await this.findById(user);
    guest.penaltiesReceived += 1;
    await guest.save();
  }
}
