// WingsEmu
// 
// Developed by NosWings Team

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using PhoenixLib.Extensions;
using PhoenixLib.Logging;
using WingsAPI.Communication;
using WingsAPI.Communication.DbServer.AccountService;
using WingsAPI.Communication.DbServer.CharacterService;
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 WingsEmu.Core.Extensions;
using WingsEmu.DTOs.Account;
using WingsEmu.DTOs.Inventory;
using WingsEmu.DTOs.Mates;
using WingsEmu.Game;
using WingsEmu.Game.Items;
using WingsEmu.Game.Managers;
using WingsEmu.Game.Networking;
using WingsEmu.Health;
using WingsEmu.Packets.ClientPackets;
using WingsEmu.Packets.Enums;

namespace WingsEmu.Plugins.PacketHandling.CharScreen;

public class EntryPointPacketHandler : GenericCharScreenPacketHandlerBase<EntryPointPacket>
{
    private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
    private readonly IAccountService _accountService;
    private readonly ICharacterService _characterService;
    private readonly IGameItemInstanceFactory _itemInstanceFactory;
    private readonly IMaintenanceManager _maintenanceManager;
    private readonly IServerManager _serverManager;
    private readonly ISessionManager _sessionManager;
    private readonly ISessionService _sessionService;

    public EntryPointPacketHandler(ISessionService sessionService, ICharacterService characterService, IMaintenanceManager maintenanceManager, IAccountService accountService,
        ISessionManager sessionManager, IServerManager serverManager, IGameItemInstanceFactory itemInstanceFactory)
    {
        _sessionService = sessionService;
        _characterService = characterService;
        _maintenanceManager = maintenanceManager;
        _accountService = accountService;
        _sessionManager = sessionManager;
        _serverManager = serverManager;
        _itemInstanceFactory = itemInstanceFactory;
    }

    public async Task EntryPointAsync(IClientSession session, EntryPointPacket packet)
    {
        await _semaphoreSlim.WaitAsync();
        try
        {
            if (session.Account == null)
            {
                SessionResponse sessionResponse = await _sessionService.GetSessionByAccountName(new GetSessionByAccountNameRequest()
                {
                    AccountName = packet.SessionName
                });

                if (sessionResponse.ResponseType != RpcResponseType.SUCCESS)
                {
                    Log.Debug($"Can't get session with ID {packet.SessionName}");
                    session.ForceDisconnect();
                    return;
                }

                Session sharedSession = sessionResponse.Session;
                if (sharedSession.State != SessionState.ServerSelection)
                {
                    Log.Debug($"Incorrect state for {sharedSession.Id}");
                    session.ForceDisconnect();
                    return;
                }

                if (session.SessionId != sessionResponse.Session.EncryptionKey)
                {
                    session.ForceDisconnect();
                    return;
                }

                SessionResponse sessionResponseAccount = await _sessionService.GetSessionByAccountId(new GetSessionByAccountIdRequest
                {
                    AccountId = sharedSession.AccountId
                });

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

                if (sessionResponseAccount.Session.EncryptionKey != session.SessionId)
                {
                    session.ForceDisconnect();
                    return;
                }

                if (sessionResponseAccount.Session.State != SessionState.ServerSelection)
                {
                    session.ForceDisconnect();
                    return;
                }

                if (_maintenanceManager.IsMaintenanceActive && sharedSession.Authority < AuthorityType.GameMaster)
                {
                    Log.Debug("[ENTRY_POINT] Maintenance is active");
                    session.ForceDisconnect();
                    return;
                }

                if (_sessionManager.SessionsCount >= _serverManager.AccountLimit && sharedSession.Authority < AuthorityType.Moderator)
                {
                    Log.Debug("[ENTRY_POINT] Account limit reached");
                    session.ForceDisconnect();
                    return;
                }

                AccountBanGetResponse banResponse = null;
                try
                {
                    banResponse = await _accountService.GetAccountBan(new AccountBanGetRequest
                    {
                        AccountId = sharedSession.AccountId
                    });
                }
                catch (Exception e)
                {
                    Log.Error($"[ENTRY_POINT][SESSION_ID: '{session.SessionId.ToString()}'] Unexpected error: ", e);
                }

                if (banResponse?.ResponseType != RpcResponseType.SUCCESS)
                {
                    Log.Warn($"[ENTRY_POINT][SESSION_ID: '{session.SessionId.ToString()}'] Failed to get account ban for accountId: '{sharedSession.AccountId}'");
                    session.ForceDisconnect();
                    return;
                }

                AccountBanDto characterPenalty = banResponse.AccountBanDto;
                if (characterPenalty != null)
                {
                    Log.Info($"[ENTRY_POINT][SESSION_ID: '{session.SessionId.ToString()}'] connected from {session.IpAddress} while being banned");
                    session.ForceDisconnect();
                    return;
                }

                AccountLoadResponse accountResponse = await _accountService.LoadAccountById(new AccountLoadByIdRequest
                {
                    AccountId = sharedSession.AccountId
                });

                if (accountResponse.ResponseType != RpcResponseType.SUCCESS)
                {
                    Log.Debug("[ENTRY_POINT] Failed to load account");
                    session.ForceDisconnect();
                    return;
                }

                AccountDTO account = accountResponse.AccountDto;
                if (!string.Equals(account.Password, packet.Password.ToSha512(), StringComparison.Ordinal))
                {
                    Log.Debug("[ENTRY_POINT] Incorrect password");
                    session.ForceDisconnect();
                    return;
                }

                var accountObject = new Account
                {
                    Id = account.Id,
                    Name = account.Name,
                    MasterAccountId = account.MasterAccountId,
                    Authority = account.Authority,
                    BankMoney = account.BankMoney,
                    Password = account.Password,
                    Language = account.Language
                };

                Log.Warn($"INITIALIZE_ACCOUNT : {account.Name}");

                SessionResponse response = await _sessionService.ConnectToWorldServer(new ConnectToWorldServerRequest
                {
                    AccountId = account.Id,
                    ChannelId = _serverManager.ChannelId,
                    ServerGroup = _serverManager.ServerGroup
                });

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

                if (response.Session == null)
                {
                    session.ForceDisconnect();
                    return;
                }


                session.InitializeAccount(accountObject, response.Session);
            }

            DbServerGetCharactersResponse response2 = await _characterService.GetCharacters(new DbServerGetCharactersRequest
            {
                AccountId = session.Account.Id
            });
            Log.Info($"[ACCOUNT_ARRIVED] {session.Account.Name}");

            // load characterlist packet for each character in CharacterDTO
            session.SendPacket("clist_start 0");

            if (response2.Characters == null)
            {
                session.SendPacket("clist_end");
                return;
            }

            foreach (CharacterDTO character in response2.Characters)
            {
                List<CharacterInventoryItemDto> inventory = character.EquippedStuffs;

                var equipment = new Dictionary<EquipmentType, GameItemInstance>();
                foreach (CharacterInventoryItemDto equipmentEntry in inventory)
                {
                    if (equipmentEntry.InventoryType != InventoryType.EquippedItems)
                    {
                        continue;
                    }

                    GameItemInstance instance = _itemInstanceFactory.CreateItem(equipmentEntry.ItemInstance);
                    if (instance == null)
                    {
                        continue;
                    }

                    equipment[instance.GameItem.EquipmentSlot] = instance;
                }

                string petlist = string.Empty;
                List<MateDTO> mates = character.NosMates;
                for (int i = 0; i < 26; i++)
                {
                    //0.2105.1102.319.0.632.0.333.0.318.0.317.0.9.-1.-1.-1.-1.-1.-1.-1.-1.-1.-1.-1.-1
                    petlist += $"{(i != 0 ? "." : "")}{(mates.Count > i ? $"{mates[i].Skin}.{mates[i].NpcMonsterVNum}" : "-1")}";
                }

                // 1 1 before long string of -1.-1 = act completion
                session.SendPacket(
                    $"clist {character.Slot} {character.Name} 0 {(byte)character.Gender} {(byte)character.HairStyle} {(byte)character.HairColor} 0 {(byte)character.Class} {character.Level} {character.HeroLevel} {(character.HideHat ? 0 : equipment.GetOrDefault(EquipmentType.Hat)?.ItemVNum ?? -1)}.{equipment.GetOrDefault(EquipmentType.Armor)?.ItemVNum ?? -1}.{equipment.GetOrDefault(EquipmentType.WeaponSkin)?.ItemVNum ?? equipment.GetOrDefault(EquipmentType.MainWeapon)?.ItemVNum ?? -1}.{equipment.GetOrDefault(EquipmentType.SecondaryWeapon)?.ItemVNum ?? -1}.{equipment.GetOrDefault(EquipmentType.Mask)?.ItemVNum ?? -1}.{equipment.GetOrDefault(EquipmentType.Fairy)?.ItemVNum ?? -1}.{equipment.GetOrDefault(EquipmentType.CostumeSuit)?.ItemVNum ?? -1}.{(character.HideHat ? 0 : equipment.GetOrDefault(EquipmentType.CostumeHat)?.ItemVNum ?? -1)} {character.JobLevel} 1 1 {petlist} {(equipment.GetOrDefault(EquipmentType.Hat)?.GameItem.IsColorable == true ? equipment.GetOrDefault(EquipmentType.Hat).Design : 0)} 0");
            }

            session.SendPacket("clist_end");
        }
        finally
        {
            _semaphoreSlim.Release();
        }
    }

    protected override async Task HandlePacketAsync(IClientSession session, EntryPointPacket packet)
    {
        await EntryPointAsync(session, packet);
    }
}