using LightlessSync.API.Data;
using LightlessSync.API.Dto.CharaData;
using LightlessSync.API.Dto.User;
using LightlessSync.Services.Mediator;
using LightlessSync.WebAPI;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;

namespace LightlessSync.Services
{
    public class LocationShareService : DisposableMediatorSubscriberBase
    {
        private readonly DalamudUtilService _dalamudUtilService;
        private readonly ApiController _apiController;
        private IMemoryCache _locations = new MemoryCache(new MemoryCacheOptions());
        private IMemoryCache _sharingStatus = new MemoryCache(new MemoryCacheOptions());
        private CancellationTokenSource _resetToken = new CancellationTokenSource();
        
        public LocationShareService(ILogger<LocationShareService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator)
        {
            _dalamudUtilService = dalamudUtilService;
            _apiController = apiController;


            Mediator.Subscribe<DisconnectedMessage>(this, (msg) =>
            {
                _resetToken.Cancel();
                _resetToken.Dispose();
                _resetToken = new CancellationTokenSource();
            });
            Mediator.Subscribe<ConnectedMessage>(this, (msg) =>
            {
                _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, apiController.DisplayName), _dalamudUtilService.GetMapData()));
                _ = RequestAllLocation();
            } );
            Mediator.Subscribe<LocationSharingMessage>(this, UpdateLocationList);
            Mediator.Subscribe<MapChangedMessage>(this,
                msg => _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, _apiController.DisplayName), _dalamudUtilService.GetMapData())));
        }

        private void UpdateLocationList(LocationSharingMessage msg)
        {
            if (_locations.TryGetValue(msg.User.UID, out _) && msg.LocationInfo.ServerId is 0)
            {
                _locations.Remove(msg.User.UID);
                return;
            }

            if ( msg.LocationInfo.ServerId is not 0 && msg.ExpireAt > DateTime.UtcNow)
            {
                AddLocationInfo(msg.User.UID, msg.LocationInfo, msg.ExpireAt);
            }
        }

        private void AddLocationInfo(string uid, LocationInfo location, DateTimeOffset expireAt)
        {
            var options = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(expireAt)
                .AddExpirationToken(new CancellationChangeToken(_resetToken.Token));
            _locations.Set(uid, location, options);
        }

        private async Task RequestAllLocation()
        {
            try
            {
                var (data, status) = await _apiController.RequestAllLocationInfo().ConfigureAwait(false);
                foreach (var dto in data)
                {
                    AddLocationInfo(dto.LocationDto.User.UID, dto.LocationDto.Location, dto.ExpireAt);
                }

                foreach (var dto in status)
                {
                    AddStatus(dto.User.UID, dto.ExpireAt);
                }
            }
            catch (Exception e)
            {
                Logger.LogError(e,"RequestAllLocation error : ");
                throw;
            }
        }

        private void AddStatus(string uid, DateTimeOffset expireAt)
        {
            var options = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(expireAt)
                .AddExpirationToken(new CancellationChangeToken(_resetToken.Token));
            _sharingStatus.Set(uid, expireAt, options);
        }

        public string GetUserLocation(string uid)
        {
            try
            {
                if (_locations.TryGetValue<LocationInfo>(uid, out var location))
                {
                    return _dalamudUtilService.LocationToString(location);
                }
                return String.Empty;
            }
            catch (Exception e)
            {
                Logger.LogError(e,"GetUserLocation error : ");
                throw;
            }
        }

        public DateTimeOffset GetSharingStatus(string uid)
        {
            try
            {
                if (_sharingStatus.TryGetValue<DateTimeOffset>(uid, out var expireAt))
                {
                    return expireAt;
                }
                return DateTimeOffset.MinValue;
            }
            catch (Exception e)
            {
                Logger.LogError(e,"GetSharingStatus error : ");
                throw;
            }
        }

        public void UpdateSharingStatus(List<string> users, DateTimeOffset expireAt)
        {
            foreach (var user in users)
            {
                AddStatus(user, expireAt);
            }
        }
        
    }
}