import {Test, TestingModule} from '@nestjs/testing';
import {QuizService} from './quiz.service';
import {getModelToken} from "@nestjs/mongoose";
import {Question} from "../schemas/question.schema";
import {Model} from "mongoose";
import {QuestionStorage} from "../schemas/question-storage.schema";
import {GuestsService} from "../guests/guests.service";
import {GuestsServiceMock} from "../mocks/guests-service.mock";
import {SharedService} from "../shared/shared.service";
import {SharedServiceMock} from "../mocks/shared-service.mock";
import {CommandBus, EventBus, ICommand} from "@nestjs/cqrs";
import {EventbusMock} from "../mocks/eventbus.mock";
import {CommandbusMock} from "../mocks/commandbus.mock";
import {FeatureflagService, IFeatureFlagStatus} from "../featureflag/featureflag.service";
import {FeatureflagServiceMock} from "../mocks/featureflag-service.mock";
import {IncreasePlayerWinningRateCommand} from "../game/commands/increase-player-winning-rate.command";
import {IncreasePlayerScoreCommand} from "../guests/command/increase-player-score.command";
import {getRandomInt} from "../helpers/rand-number";
import {CreateNewQueueItemCommand} from "../game/commands/create-new-queue-item.command";
import {GameQueueTypes} from "../schemas/game-queue.schema";
import {BeginVersusCommand} from "../game/commands/begin-versus.command"
import spyOn = jest.spyOn;
import clearAllMocks = jest.clearAllMocks;

jest.mock('../../src/helpers/rand-number');

describe('QuizService', () => {
  let service: QuizService;
  let cmdBus: CommandBus;
  let guestService: GuestsService;
  let featureFlagService: FeatureflagService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        QuizService,
        {provide: getModelToken(Question.name), useValue: Model},
        {provide: getModelToken(QuestionStorage.name), useValue: Model},
        {provide: GuestsService, useValue: GuestsServiceMock},
        {provide: SharedService, useValue: SharedServiceMock},
        {provide: EventBus, useValue: EventbusMock},
        {provide: CommandBus, useValue: CommandbusMock},
        {provide: FeatureflagService, useValue: FeatureflagServiceMock}
      ],
    }).compile();

    service = await module.resolve<QuizService>(QuizService);
    cmdBus = await module.resolve<CommandBus>(CommandBus);
    guestService = await module.resolve<GuestsService>(GuestsService);
    featureFlagService = await module.resolve<FeatureflagService>(FeatureflagService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('calculateScore()', () => {
    let cmdBusExecSpy: jest.SpyInstance<Promise<unknown>, [command: ICommand], any>;
    let getSpy;
    const questionDocumentMock = {
      text: 'test question',
      answered: false,
      valid: 'option1',
      answers: ['option1', 'option2', 'option3', 'option4'],
      answeredBy: 1,
      note: '',
      qId: 'xx-xxx-xxx',
      userAnswers: [{
        user: 1,
        time: new Date(),
        valid: false,
      }, {
        user: 2,
        time: new Date(new Date().setSeconds((new Date).getSeconds() - 5)),
        valid: false,
      }, {
        user: 3,
        time: new Date(),
        valid: true,
      }, {
          user: 4,
          time: new Date(),
          valid: false,
      }],
      scoreCalculated: false,
      save: jest.fn(),
    };
    beforeEach(() => {
      cmdBusExecSpy = jest.spyOn(cmdBus, 'execute').mockResolvedValue(null);
      getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);
    });

    it('should not calculate score if it is already calculated', async () => {
      // setup
      questionDocumentMock.scoreCalculated = true;

      // act
      await service.calculateScore();

      // validate
      expect(getSpy).toHaveBeenCalled();
      expect(cmdBusExecSpy).not.toHaveBeenCalled();
    })

    it('should assign points to winner', async () => {
      //setup
      questionDocumentMock.scoreCalculated = false;

      // act
      await service.calculateScore();

      // validate
      const validUser = questionDocumentMock.userAnswers.find(user => user.valid)
      expect(cmdBusExecSpy).toHaveBeenNthCalledWith(1,new IncreasePlayerWinningRateCommand(validUser.user, expect.anything()));
      expect(cmdBusExecSpy).toHaveBeenNthCalledWith(2, new IncreasePlayerScoreCommand(validUser.user, 1));
      expect(cmdBusExecSpy).toHaveBeenNthCalledWith(4, new IncreasePlayerScoreCommand(validUser.user, 1));
    })


    it('should randomly add penalty to last answer if rnd > 50', async () => {
      // setup
      (getRandomInt as jest.Mock).mockReturnValue(65);
      questionDocumentMock.scoreCalculated = false;
      const whoShouldGetPenalty = questionDocumentMock.userAnswers.find(x => x.user == 2);

      //act
      await service.calculateScore();

      //validate
      expect(getRandomInt).toHaveBeenCalledWith(0,100);
      expect(cmdBusExecSpy).toHaveBeenCalledWith(new CreateNewQueueItemCommand(whoShouldGetPenalty.user, GameQueueTypes.penalty));
    });

    it('should not add penalty to last answer if rnd  < 50', async () => {
      // setup
      jest.clearAllMocks();
      (getRandomInt as jest.Mock).mockReturnValue(10);
      questionDocumentMock.scoreCalculated = false;
      const whoShouldGetPenalty = questionDocumentMock.userAnswers.find(x => x.user == 2);

      //act
      await service.calculateScore();

      //validate
      expect(getRandomInt).toHaveBeenCalledWith(0,100);
      expect(cmdBusExecSpy).not.toHaveBeenCalledWith(new CreateNewQueueItemCommand(whoShouldGetPenalty.user, GameQueueTypes.penalty));

    })

    it('should set score calculated after calculation', async () => {
      // setup
      questionDocumentMock.scoreCalculated = false;
      const saveSpy = jest.spyOn(questionDocumentMock,'save').mockResolvedValue(true);
      // act
      await service.calculateScore();

      //validate
      expect(saveSpy).toHaveBeenCalled();
    });

    it('should add show results in queue', async () => {
      // setup
      questionDocumentMock.scoreCalculated = false;
      const cmdBusExecSpy = jest.spyOn(cmdBus, 'execute');
      const validUser = questionDocumentMock.userAnswers.find(user => user.valid)
      jest.spyOn(service, 'get').mockResolvedValue(questionDocumentMock as any);


      // act
      await service.calculateScore();

      // validate
      expect(cmdBusExecSpy).toHaveBeenCalledWith(new CreateNewQueueItemCommand(expect.anything(), GameQueueTypes.showresults));
    });


    it('should start versus if user replied in less than 5 seconds if ff enabled', async () => {
      // setup
      questionDocumentMock.scoreCalculated = false;
      const ffstate: IFeatureFlagStatus = {
        name: '',
        state: true,
      }
      spyOn(featureFlagService,'getFeatureFlag').mockResolvedValue(ffstate);
      questionDocumentMock.userAnswers = [{
        user: 1,
        time: new Date(),
        valid: true,
      },
        {
          user: 2,
          time: new Date(),
          valid: true,
        }
      ]
      getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);

      // act
      await service.calculateScore();

      // validate
      expect(cmdBusExecSpy).toHaveBeenCalledWith(new BeginVersusCommand(expect.anything(), expect.anything()));
    });

    it('should not start versus if FF is off and gap less than 5', async () => {
      // setup
      jest.clearAllMocks();
      questionDocumentMock.scoreCalculated = false;
      const ffstate: IFeatureFlagStatus = {
        name: '',
        state: false,
      }
      spyOn(featureFlagService,'getFeatureFlag').mockResolvedValue(ffstate);
      questionDocumentMock.userAnswers = [{
        user: 1,
        time: new Date(),
        valid: true,
      },
        {
          user: 2,
          time: new Date(),
          valid: true,
        }
      ]
      getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);

      // act
      await service.calculateScore();

      // validate
      expect(cmdBusExecSpy).not.toHaveBeenCalledWith(new BeginVersusCommand(expect.anything(), expect.anything()));

    });
    it('should not start versus if gap more than 5 seconds', async () => {
      // setup
      questionDocumentMock.scoreCalculated = false;
      questionDocumentMock.userAnswers = [{
        user: 1,
        time: new Date(new Date().setSeconds(new Date().getSeconds() - 5.7)),
        valid: true,
      },
        {
          user: 2,
          time: new Date(),
          valid: true,
        }
      ]
      getSpy = jest.spyOn(service,'get').mockResolvedValue(questionDocumentMock as any);

      // act
      await service.calculateScore();
      // validate
      expect(cmdBusExecSpy).not.toHaveBeenCalledWith(new BeginVersusCommand(expect.anything(), expect.anything()));
    });

    it('should not start versus if only one player answered correctly', () => {

    })
  });
});
