﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NW55.Applications.RuneScapeClanTracker.Data;

namespace NW55.Applications.RuneScapeClanTracker.Services
{
    // TODO more logging
    // TODO think about concurrency

    public class UpdatingService
    {
        static readonly TimeSpan minTimeBetweenUpdates = TimeSpan.FromMinutes(10);

        DataContext dataContext;
        ClanApiClient apiClient;
        ILogger<UpdatingService> logger;

        public UpdatingService(DataContext dataContext, ClanApiClient apiClient, ILogger<UpdatingService> logger)
        {
            this.dataContext = dataContext;
            this.apiClient = apiClient;
            this.logger = logger;
        }

        async Task UpdatePlayer(Player player, Models.ClanMember clanMember)
        {
            if (player.CurrentRank != clanMember.Rank)
            {
                player.CurrentRank = clanMember.Rank;
                dataContext.Players.Update(player);
                RankLogEntry rankLogEntry = new RankLogEntry()
                {
                    PlayerId = player.Id,
                    Date = DateTime.UtcNow,
                    Rank = clanMember.Rank
                };
                dataContext.RankLog.Add(rankLogEntry);
                await dataContext.SaveChangesAsync();
            }
        }

        async Task AddPlayer(Clan clan, Models.ClanMember clanMember)
        {
            RankLogEntry rankLogEntry = new RankLogEntry()
            {
                Date = DateTime.UtcNow,
                Rank = clanMember.Rank
            };
            Player player = new Player()
            {
                ClanId = clan.Id,
                Name = clanMember.Name,
                Joined = DateTime.UtcNow,
                CurrentRank = clanMember.Rank,
                RankLog = new List<RankLogEntry>() { rankLogEntry }
            };
            dataContext.Players.Add(player);
            await dataContext.SaveChangesAsync();
        }

        async Task RemovePlayerFromClan(Player player)
        {
            player.Left = DateTime.UtcNow;
            dataContext.Players.Update(player);
            await dataContext.SaveChangesAsync();
        }

        async Task UpdateClanInternal(Clan clan)
        {
            logger.LogDebug($"UpdateClanInternal(clan.id={clan.Id})");

            var clanMembers = await apiClient.GetClanMembers(clan.Name);

            clan.Updated = DateTime.UtcNow;
            dataContext.Clans.Update(clan);
            await dataContext.SaveChangesAsync();

            var players = await dataContext.Players.Where(p => p.ClanId == clan.Id && p.Left == null).ToListAsync();

            var clanMembersByName = clanMembers.ToDictionary(m => m.Name);

            foreach (Player player in players)
            {
                if (clanMembersByName.TryGetValue(player.Name, out var clanMember))
                {
                    clanMembersByName.Remove(player.Name);
                    await UpdatePlayer(player, clanMember);
                }
                else
                {
                    await RemovePlayerFromClan(player);
                }
            }

            foreach (var clanMember in clanMembersByName.Values)
            {
                await AddPlayer(clan, clanMember);
            }
        }

        public async Task<bool> UpdateClan(int clanId)
        {
            logger.LogDebug($"UpdateClan(id={clanId})");

            Clan clan = await dataContext.Clans.FirstOrDefaultAsync(c => c.Id == clanId);

            if (clan != null)
            {
                await UpdateClanInternal(clan);
                return true;
            }
            else
            {
                logger.LogWarning($"Cannot update clan. A clan with the id {clanId} does not exist.");
                return false;
            }
        }

        public async Task UpdateAllClans()
        {
            logger.LogDebug("UpdateAllClans()");

            DateTime now = DateTime.UtcNow;
            DateTime maxUpdatedTime = now - minTimeBetweenUpdates;

            var clans = await dataContext.Clans.Where(c => c.Updated < maxUpdatedTime).ToListAsync();

            logger.LogDebug($"Attempting to update {clans.Count} clans...");

            foreach (Clan clan in clans)
                await UpdateClanInternal(clan);
        }
    }
}
