import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not } from 'typeorm';

import { GameEntity, Game_PersonEntity } from './game.entity';
import { GameDTO } from './game.dto';
import { PersonEntity } from '../user/user.entity';
import { GameGroupEntity } from './group.entity';
import { FactionEntity } from './faction.entity';

@Injectable()
export class GameService {
  constructor(
    @InjectRepository(GameEntity)
    private gameRepository: Repository<GameEntity>,
    @InjectRepository(FactionEntity)
    private factionRepository: Repository<FactionEntity>,
    @InjectRepository(PersonEntity)
    private personRepository: Repository<PersonEntity>,
    @InjectRepository(Game_PersonEntity)
    private game_PersonRepository: Repository<Game_PersonEntity>,
    @InjectRepository(GameGroupEntity)
    private game_GroupRepository: Repository<GameGroupEntity>,
  ) {}

  // create a new game
  async createNewGame(personId: PersonEntity, gameData: GameDTO) {
    try {
      // checks if a game with the same name exists already
      const { name } = gameData;
      if (await this.gameRepository.findOne({ where: { name } })) {
        throw new HttpException('Game already exists', HttpStatus.BAD_REQUEST);
      }
      // else add the game to the database
      const game = await this.gameRepository.create({
        ...gameData,
        factions: gameData.factions,
      });
      await this.gameRepository.insert(game);
      const gamePerson = await this.game_PersonRepository.create({
        faction: null,
        game: game,
        person: personId,
      });
      gamePerson['role'] = 'admin';
      await this.game_PersonRepository.insert(gamePerson);
      return {
        message: 'New game added',
      };
    } catch (error) {
      return error.message;
    }
  }

  // edit already created game
  async editGame(id: string, gameData: GameDTO) {
    try {
      // checks if a game with the same name exists already
      const { name } = gameData;
      if (await this.gameRepository.findOne({ name: name, id: Not(id) })) {
        throw new HttpException('Game already exists', HttpStatus.BAD_REQUEST);
      }
      // update game entry in db
      const updatedGame = await this.gameRepository.create({
        ...gameData,
        factions: null,
      });
      updatedGame['id'] = id;
      const gameId = await this.gameRepository.save(updatedGame);
      // get all the factions that are associated with the game to deny duplicate entries

      const factions = await this.factionRepository.find(gameId);
      const factionNames = factions.map(({ factionName }) => factionName);
      console.log(factionNames);
      // add the factions to db
      if (gameData.factions) {
        gameData.factions.map(async faction => {
          if (!Object.values(factionNames).includes(faction.factionName)) {
            let name = await this.factionRepository.create({
              ...faction,
              gameId: gameId,
            });
            await this.factionRepository.insert(name);
          }
        });
      }

      return {
        message: 'Game updated',
      };
    } catch (error) {
      return error;
    }
  }

  async deleteGame(id) {
    // TODO: Delete factions from Faction table associated with the deleted game
    await this.gameRepository.delete({ id });
    return {
      message: 'Game deleted',
    };
  }

  // checks the password, creates an entry in GamePerson table with associated role&faction
  async createGroup(person, gameId, groupData) {
    try {
      // check if the person already is in a group in this game
      const checkDuplicate = await this.game_PersonRepository.findOne({
        person: person,
      });
      if (checkDuplicate) {
        throw new HttpException(
          'You already belong to a group!',
          HttpStatus.BAD_REQUEST,
        );
      }

      // create a group entry and insert it to db
      const group = await this.game_GroupRepository.create({
        ...groupData,
        game: gameId,
      });
      const gameGroup = await this.game_GroupRepository.insert(group);

      // create game_Person entry and insert it to db
      const gamePerson = await this.game_PersonRepository.create({
        role: 'soldier',
        faction: null,
        game: gameId,
        person: person,
        leaderGroup: gameGroup.identifiers[0]['id'],
        group: gameGroup.identifiers[0]['id'],
      });
      await this.game_PersonRepository.insert(gamePerson);

      return {
        message: 'created new group',
      };
    } catch (e) {
      return e;
    }
  }

  async showGroups() {
    try {
      return await this.game_GroupRepository.find({
        relations: ['leader', 'players', 'game'],
      });
    } catch (e) {
      return e;
    }
  }

  async joinGroup(person, groupId) {
    try {
      const gameData = await this.game_GroupRepository.findOne({
        where: { id: groupId },
        relations: ['players', 'game'],
      });
      const gamePerson = await this.game_PersonRepository.create({
        role: 'soldier',
        faction: null,
        game: gameData.game,
        person: person,
        leaderGroup: null,
        group: groupId,
      });
      await this.game_PersonRepository.insert(gamePerson);
      return {
        message: 'Joined group',
      };
    } catch (e) {
      return e;
    }
  }

  // returns name and id of each game
  async listGames() {
    try {
      const games = await this.gameRepository.find();
      return games.map(game => {
        return game.gameObject();
      });
    } catch (error) {
      return error;
    }
  }

  // returns information about a game identified by id
  async returnGameInfo(id: string) {
    const game = await this.gameRepository.findOne({
      where: { id: id },
      relations: ['factions'],
    });
    return game;
  }
}