using Microsoft.EntityFrameworkCore;
using PARR.API.Authentication.Models;
using PARR.API.Domain.InfluxDbModels;
using PARR.API.Services.Interfaces;
using PARR.API.Settings;
using PARR.Core.Common.Interfaces;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Domain.Cache;
using PARR.Domain.Common.Roles;
using PARR.Domain.Settings;

namespace PARR.API.Services.Implementations
{
    public class AuthService : IAuthService
    {
        private readonly IRedisCacheService cacheService;
        private readonly IUserRepository userService;
        private readonly UserCacheSettings userCacheSettings;
        private readonly IClientService clientService;
        private readonly IInfluxDbService influxDbService;
        private readonly InfluxDbSettings influxDbSettings;
        private readonly IUnitRepository unitService;

        public AuthService(
            IRedisCacheService cacheService,
            IUserRepository userService,
            UserCacheSettings userCacheSettings,
            IClientService clientService,
            IInfluxDbService influxDbService,
            InfluxDbSettings influxDbSettings,
            IUnitRepository unitService
            )
        {
            this.cacheService = cacheService;
            this.userService = userService;
            this.userCacheSettings = userCacheSettings;
            this.clientService = clientService;
            this.influxDbService = influxDbService;
            this.influxDbSettings = influxDbSettings;
            this.unitService = unitService;
        }


        public async Task<bool> UserIsBlockedAsync(string ipAddress)
        {
            //пользователь есть в списке блокировки?
            var existUserInBlockList = await cacheService.GetCachedDataAsync<DateTimeOffset?>(GetBlockKey(ipAddress));

            return existUserInBlockList != null;
        }


        public async Task<UserCache?> GetCurrentUserAsync()
        {
            var userIp = clientService.GetClientIp()?.ToString();

            if (userIp == null)
                return null;

            return await GetUserAsync(userIp);
        }


        public async Task<UserCache?> GetUserAsync(string ipAddress)
        {
            // смотрим, есть ли в кэше
            var userInCache = await cacheService.GetCachedDataAsync<UserCache>(GetAllowKey(ipAddress));
            if (userInCache != null)
                return userInCache;

            // нет в кэше
            var roles = new List<UserRoleInCache>();

            var user = await userService.GetByIpWithRolesAsync(ipAddress);
            if (user != null)
            {
                user.Roles.ToList().ForEach(t =>
                {
                    if (t.Role != null)
                        roles.Add(new UserRoleInCache { Name = t.Role.Name, Description = t.Role.Description });
                });

                // сохраняем время входа
                user.LastLogon = DateTimeOffset.UtcNow;
                await userService.CommitAsync();
            }

            var unit = await unitService.Get()
                                .Include(t => t.UnitValues).ThenInclude(t => t.Value)
                                .FirstOrDefaultAsync(t => t.UnitValues.FirstOrDefault(v => v.Value!.Value == ipAddress) != null);
            if (unit != null)
            {
                roles.Add(new UserRoleInCache { Name = ParrRoles.Agent.Role, Description = ParrRoles.Agent.Description });

                // сохраняем время входа
                unit.LastLogon = DateTimeOffset.UtcNow;
                await unitService.CommitAsync();
            }

            if (user == null && unit == null)
            {
                // если это не пользователь и не хост, добавляем в кэш на блокировку
                await cacheService.SetCachedDataAsync(GetBlockKey(ipAddress), DateTimeOffset.UtcNow, userCacheSettings.UserBlockTtl);

                return null;
            }

            // пользователь есть, добавляем в КЭШ и возвращаем роли
            var cacheData = new UserCache
            {
                UserIp = ipAddress,
                Roles = roles
            };

            if (unit != null)
            {
                cacheData.Name = unit.Name;
                cacheData.UnitId = unit.Id;
                cacheData.LastLogon = unit.LastLogon;
            }

            if (user != null)
            {
                cacheData.Name = user.Name + (unit != null ? $", {unit.Name}" : string.Empty);
                cacheData.Description = user.Description ?? string.Empty;
                cacheData.UserId = user.Id;
                cacheData.LastLogon = user.LastLogon;
            }

            await cacheService.SetCachedDataAsync(GetAllowKey(ipAddress), cacheData, userCacheSettings.UserCacheTtl);

            //тут записать в статистику входов в influxdb
            WriteLogonStatistic(cacheData);

            return cacheData;
        }


        private void WriteLogonStatistic(UserCache cacheData)
        {
            influxDbService.Write(write =>
            {
                var data = new LogonDbModel { UnitId = cacheData.UnitId, Ip = cacheData.UserIp, UserId = cacheData.UserId };
                write.WriteMeasurement(data, InfluxDB.Client.Api.Domain.WritePrecision.Ns, bucket: influxDbSettings.Logons!.Bucket, org: influxDbSettings.Organization);
            }, influxDbSettings.Logons!.Token);
        }


        public async Task UnlockUserAsync(string ipAddress)
        {
            await cacheService.DeleteCachedDataAsync(GetBlockKey(ipAddress));
        }


        public async Task RemoveCacheUserAsync(string ipAddress)
        {
            await cacheService.DeleteCachedDataAsync(GetAllowKey(ipAddress));
            await cacheService.DeleteCachedDataAsync(GetBlockKey(ipAddress));
        }


        private string GetBlockKey(string ipAddress)
        {
            // d - deny
            //return $"d_{ipAddress}";
            return cacheService.GetKey(CacheKeys.UserAuth.Banned(ipAddress));
        }

        private string GetAllowKey(string ipAddress)
        {
            // a - allow
            //return $"a_{ipAddress}";
            return cacheService.GetKey(CacheKeys.UserAuth.Session(ipAddress));
        }


    }
}
