using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
using Fantasy.Platform.Net;

// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.

namespace NB.Authentication;

public sealed class AuthenticationComponentDestroySystem : DestroySystem<AuthenticationComponent>
{
    protected override void Destroy(AuthenticationComponent self)
    {
        foreach (var (_, account) in self.Accounts.ToArray())
        {
            account.Dispose();
        }

        self.Accounts.Clear();
    }
}

internal static class AuthenticationComponentSystem
{
    public static void UpdatePosition(this AuthenticationComponent self)
    {
        // 1、通过远程接口或者本地文件来拿到鉴权组
        // 2、通过配置文件来拿
        var authentications = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Authentication);
        // 拿到当前Scene的配置文件
        var sceneConfig = SceneConfigData.Instance.Get(self.Scene.SceneConfigId);
        // 获取到当前Scene在鉴权组的位置
        self.Position = authentications.IndexOf(sceneConfig);
        // 获得鉴权组的总数
        self.AuthenticationCount = authentications.Count;
        Log.Info($"鉴权服务器启动成功！Position:{self.Position} AuthenticationCount:{self.AuthenticationCount}");
    }

    internal static async FTask<(uint ErrorCode, long AccountId, int region)> Login(this AuthenticationComponent self,
        string userName, string password)
    {
        // 1、检查传递的参数是否完整
        if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
        {
            return (ErrorCode.ErrArgs, 0, 0);
        }

        // 检查账号是否应该在当前鉴权服务器中处理
        var position = HashCodeHelper.MurmurHash3(userName) % self.AuthenticationCount;
        if (self.Position != position)
        {
            // 这个3代表的是当前账号不应该在这个鉴权服务器处理。
            // return (3, 0);
            return (ErrorCode.ErrServer, 0, 0);
        }

        var scene = self.Scene;
        var worldDateBase = scene.World.DataBase; //DateBase
        var usernameHashCode = userName.GetHashCode();

        using (var @lock =
               await scene.CoroutineLockComponent.Wait((int)LockType.AuthenticationLoginLock, usernameHashCode))
        {
            // 如果用户频繁发生登录的请求，导致服务器会频繁请求数据库或缓存。
            // 针对这个问题咱们可以利用缓存来解决这个问题。
            // 1、创建一个新的字典容器在AuthenticationComponent中存储登录的信息。
            // 2、key:userName + password, Value = ?
            // 3、为了防止缓存暴涨、肯定需要一个定期清理的过程，Value肯定是要一个实体了。
            // 4、因为这个实体下面咱们可以挂载一个组件，这个组件的作用就是定时清理这个缓存。5

            // 问题
            // 1、如果用户的密码改了怎么办?
            // 因为缓存中有定时清除的，所以遇到改密码的情况下，最多等待这个缓存清除了，然后就可以登录了。
            // 2、如果我不这样做，还有什么其他办法？
            // 通过防火墙的策略来限制用户请求，比如100ms请求一次。

            // 作业:
            // 在这个AccountCacheInfo下创建一个组件，这个组件的功能就是定时清理这个缓存。

            Account account = null;
            var loginAccountsKey = userName + password;

            if (self.LoginAccounts.TryGetValue(loginAccountsKey, out var accountCacheInfo))
            {
                account = accountCacheInfo.GetComponent<Account>();

                if (account == null)
                {
                    return (ErrorCode.ErrAccountOrPass, 0, 0);
                }

                return (ErrorCode.Successful, account.Id, account.Region);
            }

            uint result = 0;
            accountCacheInfo = Entity.Create<AccountCacheInfo>(scene, true, true);
            account = await worldDateBase.First<Account>(d => d.Username == userName);

            if (account == null)
            {
                // 没有注册
                return (ErrorCode.ErrAccountOrPass, -1, 0); //返回-1，用于判断是否需要自动注册
            }

            if (account.Password != password)
            {
                //密码错误
                result = ErrorCode.ErrAccountOrPass;
            }
            else
            {
                // 更新登录时间，并保存到数据库
                account.LoginTime = TimeHelper.Now;
                await worldDateBase.Save(account);
                // 添加Account到缓存中
                account.Deserialize(scene);
                accountCacheInfo.AddComponent(account);
            }

            accountCacheInfo.AddComponent<AccountCacheInfoTimeOut>().TimeOut(loginAccountsKey, 5000);
            self.LoginAccounts.Add(loginAccountsKey, accountCacheInfo);

            if (result != 0)
            {
                return (result, 0, 0);
            }

            return (ErrorCode.Successful, account.Id, account.Region);
        }
    }

    /// <summary>
    /// 鉴权注册接口
    /// </summary>
    /// <param name="self"></param>
    /// <param name="username"></param>
    /// <param name="region"></param>
    /// <param name="source"></param>
    internal static async FTask<uint> Register(this AuthenticationComponent self, string username, int region,
        string source)
    {
        // 1、检查传递的参数是否完整

        if (string.IsNullOrEmpty(username))
        {
            // 这个1代表的是参数不完整。
            return ErrorCode.ErrArgs;
        }

        // 检查账号是否应该在当前鉴权服务器中处理

        var position = HashCodeHelper.MurmurHash3(username) % self.AuthenticationCount;
        if (self.Position != position)
        {
            // 这个代表的是当前账号不应该在这个鉴权服务器处理。
            return ErrorCode.ErrServer;
        }

        var usernameHashCode = username.GetHashCode();
        var scene = self.Scene;

        // 利用协程锁来解决异步的原子问题
        using (var @lock =
               await scene.CoroutineLockComponent.Wait((int)LockType.AuthenticationRegisterLock, usernameHashCode))
        {
            // 利用缓存来减少频繁请求数据库或缓存的压力。

            if (self.Accounts.TryGetValue(username, out var account))
            {
                // 这个2代表的是该用户已经存在。
                return ErrorCode.ErrAccountHave;
            }

            // 2、数据库查询该账号是否存在
            var worldDateBase = scene.World.DataBase;
            var isExist = await worldDateBase.Exist<Account>(d => d.Username == username);
            if (isExist)
            {
                // 这个2代表的是该用户已经存在。
                return ErrorCode.ErrAccountHave;
            }

            //3、执行到这里的话，表示数据库或缓存没有该账号的注册信息，需要咱们创建一个。
            account = Entity.Create<Account>(scene, true, true);
            account.Username = username;
            account.Password = username;
            account.Region = region;
            account.CreateTime = TimeHelper.Now;
            // 写入这个实体到数据中
            await worldDateBase.Save(account);
            var accountId = account.Id;
            // 把当前账号添加到缓存字典中。
            self.Accounts.Add(username, account);
            // 添加AccountTimeOut组件用来定时清除缓存
            account.AddComponent<AccountTimeOut>().TimeOut(4000);
            // 这个0代表的是操作成功
            Log.Info($"Register source:{source} username:{username} accountId:{accountId}");
            return ErrorCode.Successful;
        }
    }

    internal static void RemoveLoginAccounts(this AuthenticationComponent self, string key, bool isDispose)
    {
        if (!self.LoginAccounts.Remove(key, out var accountCacheInfo))
        {
            return;
        }

        if (isDispose)
        {
            accountCacheInfo.Dispose();
        }
    }

    internal static void RemoveCache(this AuthenticationComponent self, string username, bool isDispose)
    {
        if (!self.Accounts.Remove(username, out var account))
        {
            return;
        }

        Log.Debug($"Remove cache username:{username} Count:{self.Accounts.Count}");

        if (isDispose)
        {
            account.Dispose();
        }
    }

    internal static async FTask<uint> Remove(this AuthenticationComponent self, long accountId, string source)
    {
        var scene = self.Scene;

        // 其实呢，这里没必要加协程锁，这里加是为了给大家加深下这个协程锁的印象。

        using (var @lock = await scene.CoroutineLockComponent.Wait((int)LockType.AuthenticationRemoveLock, accountId))
        {
            var worldDateBase = scene.World.DataBase;
            await worldDateBase.Remove<Account>(accountId);
            Log.Info($"Remove source:{source} accountId:{accountId}");
            return 0;
        }
    }
}