using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Helper;
using Fantasy.Network;

namespace NB.Game;

public static class PlayerHelper
{
    #region 数据保存

    public static async FTask Save(this Player self)
    {
        //先立马保存,后续做缓存
        await self.Scene.World.DataBase.Save(self);
    }

    /// <summary>
    /// 从数据库中读取GameAccount
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="accountId">账号Id</param>
    /// <returns></returns>
    public static async FTask<Player?> LoadDataBase(Scene scene, long accountId)
    {
        var account = await scene.World.DataBase.First<Player>(d => d.Id == accountId);
        if (account == null)
        {
            return null;
        }

        account.Deserialize(scene);
        return account;
    }


    /// <summary>
    /// 执行该账号的断开逻辑，不要非必要不要使用这个接口，这个接口是内部使用。
    /// </summary>
    /// <param name="self"></param>
    public static async FTask Disconnect(this Player self)
    {
        // 保存该账号信息到数据库中。
        await Save(self);
        var itemContainer = self.GetComponent<PlayerItemContainerComponent>();
        if (itemContainer != null)
        {
            await itemContainer.Save();
        }

        var wallet = self.GetComponent<PlayerWalletComponent>();
        if (wallet != null)
        {
            await wallet.Save();
        }

        // 在缓存中移除自己，并且执行自己的Dispose方法。
        self.Scene.GetComponent<PlayerManageComponent>().Remove(self.Id);
    }

    /// <summary>
    /// 账号完整的断开逻辑，执行了这个接口后，该账号会完全下线。
    /// </summary>
    /// <param name="scene"></param>
    /// <param name="accountId"></param>
    /// <param name="timeOut"></param>
    public static async FTask Disconnect(Scene scene, long accountId, int timeOut = 1000 * 60 * 3)
    {
        // 调用该方法有如下几种情况:
        // 1、客户端主动断开，如:退出游戏、切换账号、等。 客户端会主动发送一个协议给服务器通知服务器断开。
        // 2、客户端断断线 客户端不会主动发送一个协议给服务器，是由服务器的心跳来检测是否断开了。
        // 如果是心跳检测断开的Session，我怎么能拿到当前的这个账号来进行下线处理呢？
        // 通过给当前的Session挂载一个组件，当销毁这个Session时候呢，也会销毁这个组件。
        // 这样的话，是不是可以在登录的时候，给这个组件把AccountId存到这个组件呢？

        // 要检查当前缓存中是否存在该账号的数据
        var gameAccountManageComponent = scene.GetComponent<PlayerManageComponent>();
        if (!gameAccountManageComponent.TryGet(accountId, out var account))
        {
            // 如果缓存中没有、那表示已经下线或者根本不存在该账号，应该在打印一个警告，因为正常的情况下是不会出现的。
            Log.Warning($"GameAccountHelper Disconnect accountId : {accountId} not found");
            return;
        }

        // 为了防止逻辑的错误，加一个警告来排除下
        if (!scene.TryGetEntity<Session>(account.SessionRunTimeId, out var session))
        {
            // 如果没有找到对应的Session，那只有一种可能就是当前的链接会话已经断开了，一般的情况下也不会出现的，所以咱们也要打印一个警告。
            Log.Warning(
                $"GameAccountHelper Disconnect accountId : {accountId} SessionRunTimeId : {account.SessionRunTimeId} not found");
            return;
        }

        // 如果不存在定时任务的组件，那就添加并设置定时任务
        if (account.IsTimeOutComponent())
        {
            // 如果已经存在了，那就表示当然已经有一个延时断开的任务了，那就不需要重复添加了
            return;
        }

        // 立即下线处理
        if (timeOut <= 0)
        {
            // 如果timeOut销毁或等会0的情况下，执行立即下线。
            await account.Disconnect();
            return;
        }

        // 设置延迟下线
        account.SetTimeout(timeOut, account.Disconnect);
    }

    #endregion

    #region 结构转换

    public static RoleSimpleInfo GetRoleSimpleInfo(this Player self)
    {
        return new RoleSimpleInfo()
        {
            RoleId = self.Id,
            NickName = self.NickName,
            Head = self.Head,
            Country = self.Country,
            Level = self.Level,
            Vip = self.Vip,
        };
    }

    public static RoleInfo GetRoleInfo(this Player self)
    {
        var info = new RoleInfo();
        info.BaseInfo = GetRoleBaseInfo(self);
        // info.Items = self.ItemContainer.GetItemInfos();
        info.RoleId = self.RouteId;
        return info;
    }

    public static RoleBaseInfo GetRoleBaseInfo(this Player self)
    {
        var ret = new RoleBaseInfo()
        {
            NickName = self.NickName,
            Head = self.Head,
            Country = self.Country,
            Level = self.Level,
            Exp = self.Exp,
        };
        if (self.Vip > 0)
        {
            ret.VipInfo = new VipInfo();
            ret.VipInfo.Level = self.Vip;
            ret.VipInfo.OpenTime = self.VipGetTime;
            ret.VipInfo.ExpirationTime = self.VipExpirationTime;
        }

        return ret;
    }

    #endregion
}