using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PhoenixLib.DAL.Redis.Locks;
using PhoenixLib.Events;
using PhoenixLib.Logging;
using PhoenixLib.ServiceBus;
using WingsAPI.Communication;
using WingsAPI.Communication.DbServer.AccountService;
using WingsAPI.Communication.DbServer.CharacterService;
using WingsAPI.Communication.Player;
using WingsAPI.Communication.ServerApi;
using WingsAPI.Communication.ServerApi.Protocol;
using WingsAPI.Communication.Sessions;
using WingsAPI.Communication.Sessions.Model;
using WingsAPI.Communication.Sessions.Request;
using WingsAPI.Communication.Sessions.Response;
using WingsAPI.Data.Account;
using WingsAPI.Data.Character;
using WingsAPI.Game.Extensions.Families;
using WingsAPI.Game.Extensions.MinilandExtensions;
using WingsAPI.Game.Extensions.Quests;
using WingsAPI.Game.Extensions.Quicklist;
using WingsAPI.Packets.Enums;
using WingsEmu.DTOs.Bonus;
using WingsEmu.DTOs.Buffs;
using WingsEmu.DTOs.Inventory;
using WingsEmu.DTOs.Items;
using WingsEmu.DTOs.Maps;
using WingsEmu.DTOs.Mates;
using WingsEmu.DTOs.Quests;
using WingsEmu.DTOs.Quicklist;
using WingsEmu.DTOs.Skills;
using WingsEmu.DTOs.Titles;
using WingsEmu.Game;
using WingsEmu.Game._enum;
using WingsEmu.Game._i18n;
using WingsEmu.Game.Algorithm;
using WingsEmu.Game.Battle;
using WingsEmu.Game.Buffs;
using WingsEmu.Game.Characters;
using WingsEmu.Game.Characters.Events;
using WingsEmu.Game.Compliments;
using WingsEmu.Game.Configurations;
using WingsEmu.Game.Extensions;
using WingsEmu.Game.Extensions.Mates;
using WingsEmu.Game.Families;
using WingsEmu.Game.Families.Configuration;
using WingsEmu.Game.Helpers.Damages;
using WingsEmu.Game.Inventory;
using WingsEmu.Game.Items;
using WingsEmu.Game.Managers;
using WingsEmu.Game.Managers.StaticData;
using WingsEmu.Game.Maps;
using WingsEmu.Game.Mates;
using WingsEmu.Game.Mates.Events;
using WingsEmu.Game.Miniland;
using WingsEmu.Game.Miniland.Minigames;
using WingsEmu.Game.Networking;
using WingsEmu.Game.Quests;
using WingsEmu.Game.Quests.Event;
using WingsEmu.Game.Raids.Events;
using WingsEmu.Game.RainbowBattle.Event;
using WingsEmu.Game.Revival;
using WingsEmu.Game.Skills;
using WingsEmu.Game.TimeSpaces;
using WingsEmu.Packets.Enums;
using WingsEmu.Packets.Enums.Chat;
using WingsEmu.Plugins.DistributedGameEvents.PlayerEvents;

namespace WingsEmu.Plugins.BasicImplementations.Event.Characters;

public class CharacterLoadEventHandler : IAsyncEventProcessor<CharacterLoadEvent>
{
    private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
    private readonly IAccountService _accountService;
    private readonly IBCardEffectHandlerContainer _bcardHandlerContainer;
    private readonly IBuffFactory _buffFactory;
    private readonly ICharacterAlgorithm _characterAlgorithm;
    private readonly ICharacterService _characterService;
    private readonly FamilyConfiguration _familyConfiguration;
    private readonly IFamilyManager _familyManager;
    private readonly SerializableGameServer _gameServer;
    private readonly IGameItemInstanceFactory _itemInstanceFactory;
    private readonly IItemsManager _itemsManager;
    private readonly IGameLanguageService _language;
    private readonly IExpirableLockService _lock;
    private readonly IMapManager _mapManager;
    private readonly IMateEntityFactory _mateEntityFactory;
    private readonly IMessagePublisher<PlayerConnectedOnChannelMessage> _messagePublisher;
    private readonly IMinilandManager _minilandManager;
    private readonly IPlayerEntityFactory _playerEntityFactory;
    private readonly IQuestFactory _questFactory;
    private readonly IQuestManager _questManager;
    private readonly IRandomGenerator _randomGenerator;
    private readonly IRankingManager _rankingManager;
    private readonly IReputationConfiguration _reputationConfiguration;
    private readonly IRevivalManager _revivalManager;
    private readonly IServerApiService _serverApiService;


    private readonly IServerManager _serverManager;
    private readonly ISessionManager _sessionManager;
    private readonly ISessionService _sessionService;
    private readonly ISubActConfiguration _subActConfiguration;
    private readonly ITimeSpaceConfiguration _timeSpaceConfiguration;

    public CharacterLoadEventHandler(IServerManager serverManager, IGameLanguageService language, SerializableGameServer gameServer, IBuffFactory buffFactory, ISessionManager sessionManager,
        ICharacterAlgorithm characterAlgorithm, IMinilandManager minilandManager, IMessagePublisher<PlayerConnectedOnChannelMessage> messagePublisher, IRevivalManager revivalManager,
        IQuestManager questManager, FamilyConfiguration familyConfiguration, IServerApiService serverApiService, IRandomGenerator randomGenerator, IMapManager mapManager,
        IReputationConfiguration reputationConfiguration, IItemsManager itemsManager, ISubActConfiguration subActConfiguration, IRankingManager rankingManager,
        IExpirableLockService @lock, ITimeSpaceConfiguration timeSpaceConfiguration, ISessionService sessionService, IGameItemInstanceFactory itemInstanceFactory,
        ICharacterService characterService, IPlayerEntityFactory playerEntityFactory, IMateEntityFactory mateEntityFactory, IFamilyManager familyManager,
        IBCardEffectHandlerContainer bcardHandlerContainer, IQuestFactory questFactory, IAccountService accountService)
    {
        _serverManager = serverManager;
        _language = language;
        _gameServer = gameServer;
        _buffFactory = buffFactory;
        _sessionManager = sessionManager;
        _characterAlgorithm = characterAlgorithm;
        _minilandManager = minilandManager;
        _messagePublisher = messagePublisher;
        _revivalManager = revivalManager;
        _questManager = questManager;
        _familyConfiguration = familyConfiguration;
        _serverApiService = serverApiService;
        _randomGenerator = randomGenerator;
        _mapManager = mapManager;
        _reputationConfiguration = reputationConfiguration;
        _itemsManager = itemsManager;
        _subActConfiguration = subActConfiguration;
        _rankingManager = rankingManager;
        _lock = @lock;
        _timeSpaceConfiguration = timeSpaceConfiguration;
        _sessionService = sessionService;
        _itemInstanceFactory = itemInstanceFactory;
        _characterService = characterService;
        _playerEntityFactory = playerEntityFactory;
        _mateEntityFactory = mateEntityFactory;
        _familyManager = familyManager;
        _bcardHandlerContainer = bcardHandlerContainer;
        _questFactory = questFactory;
        _accountService = accountService;
    }

    public async Task HandleAsync(CharacterLoadEvent e, CancellationToken cancellation)
    {
        await _semaphoreSlim.WaitAsync();
        try
        {
            IClientSession session = e.Sender;

            byte? characterSlot = session.SelectedCharacterSlot;

            if (characterSlot is null)
            {
                session.ForceDisconnect();
                return;
            }

            if (session.HasCurrentMapInstance || session.HasSelectedCharacter)
            {
                return;
            }

            session.SelectedCharacterSlot = null;
            DbServerGetCharacterResponse response = await _characterService.GetCharacterBySlot(new DbServerGetCharacterFromSlotRequest
            {
                AccountId = session.Account.Id,
                Slot = characterSlot.Value
            });

            if (response is not { RpcResponseType: RpcResponseType.SUCCESS } || response.CharacterDto is null)
            {
                session.ForceDisconnect();
                return;
            }

            SessionResponse sessionResponse = await _sessionService.GetSessionByAccountId(new GetSessionByAccountIdRequest { AccountId = session.Account.Id });
            if (sessionResponse is not { ResponseType: RpcResponseType.SUCCESS })
            {
                await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
                    $"[POTENTIAL_DUPE][LOAD] sessionResponse not success => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
                session.ForceDisconnect();
                return;
            }

            Session storedSession = sessionResponse.Session;
            if (storedSession.ChannelId != _serverManager.ChannelId)
            {
                await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
                    $"[POTENTIAL_DUPE][LOAD] Wrong session ChannelId => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
                session.ForceDisconnect();
                return;
            }

            if (storedSession.EncryptionKey != session.SessionId)
            {
                await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
                    $"[POTENTIAL_DUPE][LOAD] Wrong encryption key => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
                session.ForceDisconnect();
                return;
            }


            AccountPenaltyGetAllResponse penaltiesResponse = null;
            try
            {
                penaltiesResponse = await _accountService.GetAccountPenalties(new AccountPenaltyGetRequest
                {
                    AccountId = session.Account.Id
                });
            }
            catch (Exception ex)
            {
                Log.Error("[CHARACTER_PRELOAD] Unexpected error: ", ex);
            }

            if (penaltiesResponse?.ResponseType != RpcResponseType.SUCCESS)
            {
                Log.Error("penaltiesResponse.AccountPenaltyDtos", new Exception());
                session.ForceDisconnect();
                return;
            }

            session.Account.Logs = penaltiesResponse.AccountPenaltyDtos == null ? new List<AccountPenaltyDto>() : penaltiesResponse.AccountPenaltyDtos.ToList();

            IPlayerEntity playerEntity = _playerEntityFactory.CreatePlayerEntity(response.CharacterDto);
            playerEntity.GameStartDate = DateTime.UtcNow;
            playerEntity.MapInstanceId = _mapManager.GetBaseMapInstanceIdByMapId(playerEntity.MapId);
            IMapInstance currentMap = _mapManager.GetMapInstance(playerEntity.MapInstanceId);

            if (currentMap != null && currentMap.IsBlockedZone(playerEntity.MapX, playerEntity.MapY))
            {
                Position pos = currentMap.GetRandomPosition();
                playerEntity.ChangePosition(pos);
            }
            else
            {
                playerEntity.ChangePosition(new Position(playerEntity.MapX, playerEntity.MapY));
            }

            playerEntity.Authority = session.Account.Authority;


            if (response.CharacterDto.ReturnPoint != null)
            {
                if (response.CharacterDto.ReturnPoint.MapId == 10000 && !session.IsGameMaster())
                {
                    response.CharacterDto.ReturnPoint.MapId = 1;
                    response.CharacterDto.ReturnPoint.MapX = 1;
                    response.CharacterDto.ReturnPoint.MapY = 1;
                }
            }

            playerEntity.HomeComponent.ChangeReturn(response.CharacterDto.ReturnPoint);
            playerEntity.HomeComponent.ChangeAct5Respawn(response.CharacterDto.Act5RespawnType);
            playerEntity.HomeComponent.ChangeRespawn(response.CharacterDto.RespawnType);

            LoadQuicklist(playerEntity);
            LoadInventory(playerEntity);
            LoadSkills(playerEntity);
            LoadScripts(playerEntity);
            LoadQuests(playerEntity);
            LoadTitleBCards(playerEntity);

            sessionResponse = await _sessionService.GetSessionByAccountId(new GetSessionByAccountIdRequest { AccountId = session.Account.Id });
            if (sessionResponse is not { ResponseType: RpcResponseType.SUCCESS })
            {
                await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
                    $"[POTENTIAL_DUPE][LOAD] 2nd check sessionResponse not success => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
                session.ForceDisconnect();
                return;
            }

            storedSession = sessionResponse.Session;
            if (storedSession.ChannelId != _serverManager.ChannelId)
            {
                await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
                    $"[POTENTIAL_DUPE][LOAD] 2nd check Wrong session ChannelId => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
                session.ForceDisconnect();
                return;
            }

            if (storedSession.EncryptionKey != session.SessionId)
            {
                await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER,
                    $"[POTENTIAL_DUPE][LOAD] 2nd check Wrong encryption key => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}");
                session.ForceDisconnect();
                return;
            }

            IClientSession tmp = _sessionManager.GetSessionByCharacterId(playerEntity.Id);
            if (tmp != null)
            {
                await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] {playerEntity.Name} tried to connect twice on {_serverManager.ChannelId}!");
                tmp.ForceDisconnect();
                session.ForceDisconnect();
                return;
            }

            // here everything starts
            session.InitializePlayerEntity(playerEntity);

            playerEntity.Miniland ??= _minilandManager.CreateMinilandByCharacterSession(session);

            FamilyMembership familyMember = _familyManager.GetFamilyMembershipByCharacterId(session.PlayerEntity.Id);
            playerEntity.SetFamilyMembership(familyMember);

            foreach (MateDTO mateDto in session.PlayerEntity.NosMates)
            {
                IMateEntity mate = _mateEntityFactory.CreateMateEntity(session.PlayerEntity, mateDto);

                await session.EmitEventAsync(new MateInitializeEvent
                {
                    MateEntity = mate,
                    IsOnCharacterEnter = true
                });
            }

            LoadPartnerWarehouse(playerEntity);
            LoadPartnerInventory(playerEntity);
            LoadPartnerEqBCards(playerEntity);

            await session.EmitEventAsync(new MinigameRefreshProductionEvent(false));
            await session.EmitEventAsync(new QuestDailyRefreshEvent { Force = false });
            await session.EmitEventAsync(new ComplimentsMonthlyRefreshEvent { Force = false });
            await session.EmitEventAsync(new RainbowBattleLeaverBusterRefreshEvent { Force = false });
            await session.EmitEventAsync(new SpecialistRefreshEvent { Force = false });
            await session.EmitEventAsync(new RaidResetRestrictionEvent());

            session.CurrentMapInstance = session.PlayerEntity.MapInstance;

            // need to improve this logic
            if (await PreLoadLogicAndCheckChannelChange(session))
            {
                Log.Warn($"[CHARACTER_LOAD] {session.PlayerEntity.Name} changed to another channel");
                return;
            }

            IFamily family = session.PlayerEntity.Family;

            await ExecuteInitialMapJoin(session, family);

            await TryToSendSomeWarningMessages(session);

            // todo dump
            _sessionManager.AddOnline(new ClusterCharacterInfo
            {
                ChannelId = (byte?)_gameServer.ChannelId,
                Id = session.PlayerEntity.Id,
                Name = session.PlayerEntity.Name,
                Class = session.PlayerEntity.Class,
                Gender = session.PlayerEntity.Gender,
                HeroLevel = session.PlayerEntity.HeroLevel,
                Level = session.PlayerEntity.Level,
                HardwareId = session.HardwareId
            });

            if (family != null)
            {
                FamilyPacketExtensions.SendFamilyMembersInfoToMembers(family, _sessionManager, _familyConfiguration);
            }

            await _messagePublisher.PublishAsync(new PlayerConnectedOnChannelMessage
            {
                ChannelId = _gameServer.ChannelId,
                CharacterId = session.PlayerEntity.Id,
                CharacterName = session.PlayerEntity.Name,
                Class = session.PlayerEntity.Class,
                Gender = session.PlayerEntity.Gender,
                HeroLevel = session.PlayerEntity.HeroLevel,
                Level = session.PlayerEntity.Level,
                FamilyId = family?.Id,
                HardwareId = session.HardwareId
            }, cancellation);
        }
        finally
        {
            _semaphoreSlim.Release();
        }
    }

    private async Task<bool> PreLoadLogicAndCheckChannelChange(IClientSession session)
    {
        await RefreshRevivalInfo(session);

        await LoadBuffs(session);

        if (session.CurrentMapInstance != null && !session.CurrentMapInstance.HasMapFlag(MapFlags.ACT_4))
        {
            return false;
        }

        if (session.PlayerEntity.Faction == FactionType.Neutral)
        {
            return false;
        }

        GetChannelInfoResponse response = null;
        try
        {
            response = await _serverApiService.GetAct4ChannelInfo(new GetAct4ChannelInfoRequest
            {
                WorldGroup = _serverManager.ServerGroup
            });
        }
        catch (Exception e)
        {
            Log.Error("[CHARACTER_LOAD] Unexpected error while trying to obtain channel's Act4 information: ", e);
        }

        SerializableGameServer gameServer = response?.GameServer;

        if (response?.ResponseType != RpcResponseType.SUCCESS || gameServer == null)
        {
            session.ChangeMap(1, 149, 60);
            return false;
        }

        if (gameServer.ChannelId == _gameServer.ChannelId)
        {
            return false;
        }

        short mapId = session.PlayerEntity.Faction == FactionType.Angel ? (short)MapIds.ACT4_ANGEL_CITADEL : (short)MapIds.ACT4_DEMON_CITADEL;
        short mapX = (short)(12 + _randomGenerator.RandomNumber(-2, 3));
        short mapY = (short)(40 + _randomGenerator.RandomNumber(-2, 3));

        if (_mapManager.HasMapFlagByMapId(session.PlayerEntity.MapId, MapFlags.ACT_4))
        {
            mapId = (short)session.PlayerEntity.MapId;
            mapX = session.PlayerEntity.MapX;
            mapY = session.PlayerEntity.MapY;
        }

        await session.EmitEventAsync(new PlayerChangeChannelEvent(gameServer, ItModeType.ToAct4, mapId, mapX, mapY));
        return true;
    }

    private async Task LoadBuffs(IClientSession session)
    {
        var toAdd = new List<Buff>();
        var toRemove = new List<CharacterStaticBuffDto>();
        foreach (CharacterStaticBuffDto buff in session.PlayerEntity.StaticBuffs)
        {
            if (buff.RemainingTime <= 0)
            {
                toRemove.Add(buff);
                continue;
            }

            toAdd.Add(_buffFactory.CreateBuff(buff.CardId, session.PlayerEntity, TimeSpan.FromMilliseconds(buff.RemainingTime), BuffFlag.BIG_AND_KEEP_ON_LOGOUT));
        }

        await session.PlayerEntity.AddBuffsAsync(toAdd);
        foreach (CharacterStaticBuffDto removeBuff in toRemove)
        {
            session.PlayerEntity.StaticBuffs.Remove(removeBuff);
        }
    }

    private async Task RefreshRevivalInfo(IClientSession session)
    {
        foreach (IMateEntity mate in session.PlayerEntity.MateComponent.GetMates())
        {
            _revivalManager.TryUnregisterRevival(mate.Id);
        }

        if (session.PlayerEntity.IsAlive())
        {
            return;
        }

        await session.EmitEventAsync(new RevivalReviveEvent(RevivalType.DontPayRevival)
        {
            Forced = ForcedType.Reconnect
        });
    }


    private async Task TryToSendSomeWarningMessages(IClientSession session)
    {
        await session.EmitEventAsync(new InventoryExpiredItemsEvent());

        await session.EmitEventAsync(new CharacterBonusExpiredEvent());

        if (session.PlayerEntity.HaveStaticBonus(StaticBonusType.BazaarMedalGold) || session.PlayerEntity.HaveStaticBonus(StaticBonusType.BazaarMedalSilver))
        {
            session.SendChatMessage(_language.GetLanguage(GameDialogKey.INFORMATION_CHATMESSAGE_ON_LOGIN_BAZAAR_MEDAL, session.UserLanguage), ChatMessageColorType.Green);
        }

        if (!string.IsNullOrWhiteSpace(session.PlayerEntity.Family?.Message))
        {
            session.SendInfo("--- Family Message ---\n" + session.PlayerEntity.Family.Message);
        }

        foreach (IMateEntity mate in session.PlayerEntity.MateComponent.GetMates())
        {
            session.SendCondMate(mate);
            if (mate.IsTeamMember)
            {
                await session.EmitEventAsync(new MateJoinTeamEvent { MateEntity = mate, IsOnCharacterEnter = true });
                continue;
            }

            await session.EmitEventAsync(new MateStayInsideMinilandEvent { MateEntity = mate, IsOnCharacterEnter = true });
        }

        if (session.PlayerEntity.HaveStaticBonus(StaticBonusType.PetBasket))
        {
            session.SendPetBasketPacket(true);
        }

        if (_gameServer.ChannelType == GameChannelType.ACT_4)
        {
            bool isLosingRep = await _lock.ExistsTemporaryLock($"game:locks:character:{session.PlayerEntity.Id}:act-4-less-rep");
            if (isLosingRep)
            {
                session.PlayerEntity.IsGettingLosingReputation = true;
                session.SendChatMessage(session.GetLanguage(GameDialogKey.ACT4_CHATMESSAGE_LESS_REPUTATION), ChatMessageColorType.Red);
            }
        }

        AccountPenaltyDto mute = session.Account.Logs.FirstOrDefault(x => x.PenaltyType == PenaltyType.Muted && x.RemainingTime.HasValue);
        if (mute == null)
        {
            return;
        }

        DateTime now = DateTime.UtcNow;
        session.PlayerEntity.MuteRemainingTime = TimeSpan.FromSeconds(mute.RemainingTime ?? 0);
        session.PlayerEntity.LastChatMuteMessage = DateTime.MinValue;
        session.PlayerEntity.LastMuteTick = now;
    }

    private async Task ExecuteInitialMapJoin(IClientSession session, IFamily family)
    {
        session.RefreshSkillList();

        session.SendRsfiPacket(_subActConfiguration, _timeSpaceConfiguration);

        IReadOnlyList<CharacterDTO> topReputation = _rankingManager.TopReputation;
        session.RefreshReputation(_reputationConfiguration, topReputation);
        session.RefreshPassiveBCards();
        session.SendTitlePacket();
        session.SendIncreaseRange();

        session.ShowInventoryExtensions();
        session.SendStaticBonuses();
        session.SendMinilandPrivateInformation(_minilandManager, _language);

        session.SendTitlePacket();

        session.SendZzimPacket();
        session.SendTwkPacket();

        session.RefreshFaction();
        session.SendMessageUnderChat();

        session.SendScpStcPacket();
        session.SendScpPackets();
        session.SendScnPackets();

        session.SendStartStartupInventory();

        session.RefreshGold();

        session.SendClinitPacket(_rankingManager.TopCompliment);
        session.SendFlinitPacket(topReputation);
        session.SendKdlinitPacket(_rankingManager.TopPoints);

        session.SendPacket("skyinit 0");
        session.SendPacket("skyinit 1");
        session.SendPacket("skyinit 2");
        session.SendPacket("skyinit 3");
        session.SendPacket("tbf  1.0 2.0 3.0 4.0 5.0");

        session.SendScpPackets();
        session.SendScnPackets();

        session.RefreshSpPoint();
        session.SendPacket("rage 0 250000");
        session.SendPacket("rank_cool 0 0 18000");

        bool changeMap = false;
        if (family != null)
        {
            if ((byte)session.PlayerEntity.Faction != family.Faction)
            {
                await session.EmitEventAsync(new ChangeFactionEvent
                {
                    NewFaction = family.Faction == (byte)FactionType.Angel ? FactionType.Angel : FactionType.Demon
                });

                if (session.CurrentMapInstance.HasMapFlag(MapFlags.ACT_4))
                {
                    changeMap = true;
                }
            }

            session.BroadcastGidx(family, _language);
            session.RefreshFamilyInfo(family, _familyConfiguration);
            session.SendFamilyLogsToMember(family);
            session.RefreshFamilyMembers(_sessionManager, family);
            session.RefreshFamilyMembersMessages(family);
            session.RefreshFamilyMembersExp(family);
            session.SendFmiPacket();
            session.SendFmpPacket(_itemsManager);
        }

        session.RefreshQuicklist();

        session.ChangeToLastBaseMap();

        session.SendPacket("scr 0 0 0 0 0 0");

        await ExecuteQuestLogic(session);

        if (!changeMap)
        {
            return;
        }

        await session.Respawn();
    }

    private async Task ExecuteQuestLogic(IClientSession session)
    {
        CompletedScriptsDto lastCompletedScriptDto = session.PlayerEntity.GetLastCompletedScript();
        if (lastCompletedScriptDto == null)
        {
            session.SendScriptPacket(1, 10);
            return;
        }


        TutorialDto lastCompletedTutorial = _questManager.GetScriptTutorialByIndex(lastCompletedScriptDto.ScriptId, lastCompletedScriptDto.ScriptIndex);


        if (lastCompletedTutorial == null)
        {
            return;
        }

        TutorialDto nextScript = _questManager.GetScriptTutorialById(lastCompletedTutorial.Id + 1);

        // We check if it was the last script from that sub-questline, for sending a QnpcPacket instead of a new script
        if (lastCompletedTutorial.ScriptIndex == _questManager.GetScriptsTutorialByScriptId(lastCompletedTutorial.ScriptId).Max(s => s.ScriptIndex))
        {
            QuestNpcDto questNpc = _questManager.GetQuestNpcByScriptId(lastCompletedTutorial.ScriptId + 1);
            session.SendQnpcPacket(questNpc.Level, questNpc.NpcVnum, questNpc.MapId);
            await CheckCompletedQuests(session);
            return;
        }

        // We check if we have an active quest-script
        CompletedScriptsDto lastCompletedQuestStartScript = session.PlayerEntity.GetLastCompletedScriptByType(TutorialActionType.START_QUEST);
        if (lastCompletedQuestStartScript != null)
        {
            TutorialDto lastCompletedQuestStartTutorial = _questManager.GetScriptTutorialByIndex(lastCompletedQuestStartScript.ScriptId, lastCompletedQuestStartScript.ScriptIndex);
            TutorialDto waitQuestCompletionScript = _questManager.GetScriptsTutorialByType(TutorialActionType.WAIT_FOR_QUEST_COMPLETION)
                .First(s => s.Data == lastCompletedQuestStartTutorial.Data && s.Id > lastCompletedQuestStartTutorial.Id);
            if (!session.PlayerEntity.HasCompletedScriptByIndex(waitQuestCompletionScript.ScriptId, waitQuestCompletionScript.ScriptIndex))
            {
                if (!session.PlayerEntity.HasQuestWithId(lastCompletedQuestStartTutorial.Data))
                {
                    session.PlayerEntity.RemoveCompletedScript(lastCompletedQuestStartTutorial.ScriptId, lastCompletedQuestStartTutorial.ScriptIndex);
                    var completedScriptsToRemove = session.PlayerEntity.GetCompletedScripts().Where(s => s.ScriptId > lastCompletedQuestStartTutorial.ScriptId ||
                        s.ScriptId == lastCompletedQuestStartTutorial.ScriptId && s.ScriptIndex > lastCompletedQuestStartTutorial.ScriptIndex).ToList();
                    foreach (CompletedScriptsDto completedScript in completedScriptsToRemove)
                    {
                        session.PlayerEntity.RemoveCompletedScript(completedScript.ScriptId, completedScript.ScriptIndex);
                    }

                    nextScript = lastCompletedQuestStartTutorial;
                }
                else
                {
                    nextScript = waitQuestCompletionScript;
                }
            }
        }

        session.SendScriptPacket(nextScript.ScriptId, nextScript.ScriptIndex);
        await CheckCompletedQuests(session);
    }

    private async Task CheckCompletedQuests(IClientSession session)
    {
        foreach (CharacterQuest characterQuest in session.PlayerEntity.GetCurrentQuests())
        {
            if (session.PlayerEntity.IsQuestCompleted(characterQuest))
            {
                await session.EmitEventAsync(new QuestCompletedEvent(characterQuest, true));
            }
        }

        session.RefreshQuestList(_questManager, null);
        session.SendQuestsTargets();
        session.SendSqstPackets(_questManager);
    }

    private void LoadSkills(IPlayerEntity session)
    {
        foreach (CharacterSkillDTO skillDto in session.LearnedSkills)
        {
            if (session.CharacterSkills.ContainsKey(skillDto.SkillVNum))
            {
                continue;
            }

            var skill = new CharacterSkill
            {
                SkillVNum = skillDto.SkillVNum
            };

            session.CharacterSkills[skill.SkillVNum] = skill;
            session.Skills.Add(skill);

            short upgradeSkill = skill.Skill?.UpgradeSkill ?? 0;

            if (upgradeSkill == 0)
            {
                continue;
            }

            if (!session.SkillComponent.SkillUpgrades.TryGetValue(upgradeSkill, out HashSet<IBattleEntitySkill> hashSet))
            {
                hashSet = new HashSet<IBattleEntitySkill>();
                session.SkillComponent.SkillUpgrades[upgradeSkill] = hashSet;
            }

            if (hashSet.Contains(skill))
            {
                continue;
            }

            hashSet.Add(skill);
        }
    }

    private void LoadScripts(IPlayerEntity session)
    {
        IEnumerable<CompletedScriptsDto> completedScriptsDtos = session.CompletedScripts;
        foreach (CompletedScriptsDto completedScriptDto in completedScriptsDtos)
        {
            TutorialDto completedScript = _questManager.GetScriptTutorialByIndex(completedScriptDto.ScriptId, completedScriptDto.ScriptIndex);

            if (completedScript == null)
            {
                continue;
            }

            session.SaveScript(completedScript.ScriptId, completedScript.ScriptIndex, completedScript.Type, completedScriptDto.CompletedDate);
        }
    }


    private void LoadQuests(IPlayerEntity session)
    {
        IEnumerable<CharacterQuestDto> characterQuests = session.ActiveQuests;
        IEnumerable<CharacterQuestDto> completedPeriodicQuests = session.CompletedPeriodicQuests;
        IEnumerable<CharacterQuestDto> completedQuests = session.CompletedQuests;

        foreach (CharacterQuestDto characterQuestDto in completedPeriodicQuests)
        {
            CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType);
            characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount;
            session.AddCompletedPeriodicQuest(characterQuest);
        }

        // This can be removed when the wipe of the server happens.
        // It's gonna contain only the MAIN quests, since we have a "backup" (the scripts), but 
        // with new characters will also contain GENERAL and SECONDARY ones
        if (!completedQuests.Any())
        {
            foreach (CompletedScriptsDto completedScript in session.GetCompletedScriptsByType(TutorialActionType.START_QUEST))
            {
                TutorialDto script = _questManager.GetScriptTutorialByIndex(completedScript.ScriptId, completedScript.ScriptIndex);

                if (script == null)
                {
                    continue;
                }

                if (characterQuests.Any(s => s.QuestId == script.Data)) // It's an active one
                {
                    continue;
                }

                CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, script.Data, QuestSlotType.MAIN);
                session.AddCompletedQuest(characterQuest);
            }
        }

        else
        {
            foreach (CharacterQuestDto characterQuestDto in completedQuests)
            {
                CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType);
                characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount;
                session.AddCompletedQuest(characterQuest);
            }
        }

        foreach (CharacterQuestDto characterQuestDto in characterQuests)
        {
            CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType);
            characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount;
            session.AddActiveQuest(characterQuest);
        }
    }

    private void LoadPartnerEqBCards(IPlayerEntity playerEntity)
    {
        foreach (IMateEntity mate in playerEntity.MateComponent.GetMates(x => x.MateType == MateType.Partner))
        {
            foreach (PartnerInventoryItem partnerInventory in playerEntity.PartnerGetEquippedItems(mate.PetSlot))
            {
                if (partnerInventory?.ItemInstance == null)
                {
                    continue;
                }

                mate.RefreshEquipmentValues(partnerInventory.ItemInstance, false);
            }
        }
    }

    private void LoadTitleBCards(IPlayerEntity playerEntity)
    {
        CharacterTitleDto equippedTitle = playerEntity.Titles.FirstOrDefault(x => x.IsEquipped);
        if (equippedTitle == null)
        {
            return;
        }

        playerEntity.RefreshTitleBCards(_itemsManager, equippedTitle, _bcardHandlerContainer);
    }

    private void LoadPartnerInventory(IPlayerEntity playerEntity)
    {
        IEnumerable<CharacterPartnerInventoryItemDto> partnerItems = playerEntity.PartnerInventory;
        foreach (CharacterPartnerInventoryItemDto partnerItem in partnerItems)
        {
            if (partnerItem == null)
            {
                continue;
            }

            GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(partnerItem.ItemInstance);
            playerEntity.PartnerEquipItem(itemInstance, partnerItem.PartnerSlot);
        }
    }

    private void LoadQuicklist(IPlayerEntity playerEntity)
    {
        List<CharacterQuicklistEntryDto> inventory = playerEntity.Quicklist;
        foreach (CharacterQuicklistEntryDto item in inventory)
        {
            if (item == null)
            {
                continue;
            }

            playerEntity.QuicklistComponent.AddQuicklist(item);
        }
    }

    private void LoadInventory(IPlayerEntity playerEntity)
    {
        foreach (CharacterInventoryItemDto item in playerEntity.Inventory)
        {
            if (item == null)
            {
                continue;
            }

            CharacterInventoryItemDto inventoryItem = item;

            GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(inventoryItem.ItemInstance);
            var newItem = new InventoryItem
            {
                CharacterId = inventoryItem.CharacterId,
                InventoryType = inventoryItem.InventoryType,
                IsEquipped = inventoryItem.IsEquipped,
                Slot = inventoryItem.Slot,
                ItemInstance = itemInstance
            };

            if (newItem.InventoryType == InventoryType.EquippedItems && newItem.ItemInstance != null)
            {
                playerEntity.EquipItem(newItem, newItem.ItemInstance.GameItem.EquipmentSlot, true);
                if (itemInstance.Type == ItemInstanceType.WearableInstance)
                {
                    playerEntity.RefreshEquipmentValues(itemInstance);
                }

                continue;
            }

            playerEntity.AddItemToInventory(newItem);
        }

        // equipped items
        DateTime date = DateTime.UtcNow;
        foreach (CharacterInventoryItemDto item in playerEntity.EquippedStuffs)
        {
            if (item == null)
            {
                continue;
            }

            GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(item.ItemInstance);
            var newItem = new InventoryItem
            {
                CharacterId = item.CharacterId,
                InventoryType = item.InventoryType,
                IsEquipped = item.IsEquipped,
                Slot = item.Slot,
                ItemInstance = itemInstance
            };
            if (newItem.InventoryType != InventoryType.EquippedItems || newItem.ItemInstance == null)
            {
                continue;
            }

            if (newItem.ItemInstance.ItemDeleteTime <= date)
            {
                continue;
            }

            playerEntity.EquipItem(newItem, newItem.ItemInstance.GameItem.EquipmentSlot, true);
            if (itemInstance.Type == ItemInstanceType.WearableInstance)
            {
                playerEntity.RefreshEquipmentValues(itemInstance);
            }
        }
    }

    private void LoadPartnerWarehouse(IPlayerEntity playerEntity)
    {
        IEnumerable<PartnerWarehouseItemDto> items = playerEntity.PartnerWarehouse;
        foreach (PartnerWarehouseItemDto item in items)
        {
            if (item == null)
            {
                continue;
            }

            playerEntity.AddPartnerWarehouseItem(_itemInstanceFactory.CreateItem(item.ItemInstance), item.Slot);
        }
    }
}