using AutoMapper;
using SessionCompanion.Database.Repositories.Base;
using SessionCompanion.Database.Tables;
using SessionCompanion.Services.Base;
using SessionCompanion.Services.Interfaces;
using SessionCompanion.ViewModels.CharacterViewModels;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore;
using AutoMapper.QueryableExtensions;
using System.IO;
using Newtonsoft.Json.Linq;
using SessionCompanion.ViewModels.UniversalModels;
using SessionCompanion.Services.Helpers;
using SessionCompanion.ViewModels.RaceViewModels;
using SessionCompanion.ViewModels.ClassViewModels;

namespace SessionCompanion.Services.Services
{
    public class CharacterService : ServiceBase<CharacterViewModel, Character>, ICharacterService
    {
        public CharacterService(IMapper mapper, IRepository<Character> repository, IRepository<Class> classRepository) : base(mapper, repository)
        { }

        /// <summary>
        /// Funkcja zwraca listę postaci przypisanych do podanego użytkownika
        /// </summary>
        /// <param name="userId">identyfikator użytkownika</param>
        /// <returns>Lista postaci dosępnych dla podanego użytkownika</returns>
        public async Task<IEnumerable<CharacterForLoginViewModel>> GetUserLoginCharacters(int userId)
        {
            var characters = await Repository.Get(c => c.UserId.Equals(userId))
                                 .Include(x => x.Biography)
                                 .Include(x => x.Statistics)
                                 .Include(x => x.Biography).ThenInclude(b => b.Class).ToListAsync();
            var result = Mapper.Map<IEnumerable<CharacterForLoginViewModel>>(characters);
            return result;
        }

        /// <summary>
        /// Funkcja zwraca podstawowy widok postaci na podstawie ich id
        /// </summary>
        /// <param name="charactersId">Lista identyfikatorów postaci</param>
        /// <returns>Podstawowy widok podanych postaci</returns>
        public async Task<IEnumerable<CharacterBasicStatsViewModel>> GetBasicCharactersData(List<int> charactersId)
        {
            var characters = await Repository.Get(c => charactersId.Contains(c.Id))
                                                .Include(x => x.Biography)
                                                .ThenInclude(x => x.Class)
                                                .Include(x => x.Statistics).ToListAsync();
            var result = Mapper.Map<IEnumerable<CharacterBasicStatsViewModel>>(characters);
            return result;
        }
        /// <summary>
        /// Funkcja zwraca wszystkie statystyki danej postaci
        /// </summary>
        /// <param name="characterId">indentyfikator postaci</param>
        /// <returns>ViewModel z statystykami postaci</returns>
        public async Task<CharacterEveryStatViewModel> GetCharacterEveryStat(int characterId)
        {
            var character = await Repository.Get(c => c.Id.Equals(characterId))
                .Include(x => x.Intelligence)
                .Include(x => x.Strength)
                .Include(x => x.Wisdom)
                .Include(x => x.Charisma)
                .Include(x => x.Constitution)
                .Include(x => x.Dexterity).SingleAsync();
            var result = Mapper.Map<CharacterEveryStatViewModel>(character);
            return result;
        }
        /// <summary>
        /// Funkcja zwraca listę, zawierającą statystyki danej postaci
        /// </summary>
        /// <param name="characterId"></param>
        /// <returns>lista zawierającą statystyki danej postaci</returns>
        public async Task<List<UniversalStatisticViewModel>> GetCharacterStatistics(int characterId)
        {
            List<UniversalStatisticViewModel> statistics = new List<UniversalStatisticViewModel>();
            var character = await Repository.Get(c => c.Id.Equals(characterId))
                .Include(x => x.Intelligence)
                .Include(x => x.Strength)
                .Include(x => x.Wisdom)
                .Include(x => x.Charisma)
                .Include(x => x.Constitution)
                .Include(x => x.Dexterity)
                .SingleAsync();

            statistics.Add(CustomMappings.MapCharisma(character.Charisma));
            statistics.Add(CustomMappings.MapDexterity(character.Dexterity));
            statistics.Add(CustomMappings.MapConstitution(character.Constitution));
            statistics.Add(CustomMappings.MapIntelligence(character.Intelligence));
            statistics.Add(CustomMappings.MapStrength(character.Strength));
            statistics.Add(CustomMappings.MapWisdom(character.Wisdom));

            return statistics;
        }

        /// <summary>
        /// Funkcja zwraca podstawowy widok postaci na podstawie ich id
        /// </summary>
        /// <param name="characterId">Lista identyfikatorów postaci</param>
        /// <returns>Podstawowy widok podanych postaci</returns>
        public async Task<CharacterBasicInfoViewModel> GetBasicCharacterbasicInfo(int characterId)
        {
            var character = await Repository.Get(c => c.Id.Equals(characterId))
                                                .Include(x => x.Biography).ThenInclude(x => x.Class)
                                                .Include(x => x.Biography).ThenInclude(x => x.Race)
                                                .Include(x => x.Statistics).SingleAsync();
            var result = Mapper.Map<CharacterBasicInfoViewModel>(character);
            return result;
        }

        /// <summary>
        /// Function returns basic stats from Characters in templates
        /// </summary>
        /// <returns></returns>
        public async Task<IEnumerable<CharacterFromTemplatesSimpleViewModel>> GetCharactersFromTemplate(List<RaceViewModel> raceViewModels, List<ClassViewModel> classViewModels)
        {
            const string file = "../SessionCompanion.Database/JsonData/characters_templates.json";
            List<CharacterFromTemplatesSimpleViewModel> basicStats = new List<CharacterFromTemplatesSimpleViewModel>();
            using (StreamReader reader = new StreamReader(file))
            {
                var json = await reader.ReadToEndAsync();
                foreach (var item in JArray.Parse(json))
                {
                    CharacterFromTemplatesSimpleViewModel characterFromTemplatesSimpleViewModel = new CharacterFromTemplatesSimpleViewModel()
                    {
                        Id = (int)item["id"],
                        Class = classViewModels.Where(c => c.Id.Equals((int)item["biography"]["ClassId"])).Select(c => c.Name).Single(),
                        Race = raceViewModels.Where(r => r.Id.Equals((int)item["biography"]["RaceId"])).Select(r => r.Name).Single(),
                        Name = (string)item["biography"]["Name"]
                    };
                    basicStats.Add(characterFromTemplatesSimpleViewModel);
                }
            }
            return basicStats;
        }
        /// <summary>
        /// Function creates chosen template Character for given User, it can also change its name
        /// </summary>
        /// <param name="characterId"></param>
        /// <param name="userId"></param>
        /// <param name="newName"></param>
        /// <returns></returns>
        public async Task CreateCharactersFromTemplate(int characterId, int userId, string newName)
        {
            const string file = "../SessionCompanion.Database/JsonData/characters_templates.json";
            List<Character> characters = new List<Character>();
            using (StreamReader reader = new StreamReader(file))
            {
                var json = await reader.ReadToEndAsync();
                characters = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Character>>(json);
            }
            var character = characters.Where(x => x.Id.Equals(characterId)).Single();

            character.Id = 0;
            character.UserId = userId;
            if (newName != null)
                character.Biography.Name = newName;

            await Repository.Create(character);
            await Repository.Save();
        }
    }
}