using System.Runtime.InteropServices;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using MySqlConnector;
using Dapper;
using Microsoft.Extensions.Logging;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Menu;

namespace CustomWeapons;
public class CustomWeapons : BasePlugin, IPluginConfig<CustomWeapons_Config>
{
    public override string ModuleName => "Custom Weapons";
    public override string ModuleAuthor => "Ganter1234 & schwarper";
    public override string ModuleVersion => "1.4";
    public CustomWeapons_Config Config { get; set; } = new();
    public string dbConnectionString = string.Empty;
    public string[] accessWeapons = new string[65];
    public Dictionary<string, string>[] WeaponInfo = new Dictionary<string, string>[65];
    public override void Load(bool hotReload)
    {
        if (CoreConfig.FollowCS2ServerGuidelines)
        {
            throw new Exception($"Cannot set or get 'CEconEntity::m_OriginalOwnerXuidLow' with \"FollowCS2ServerGuidelines\" option enabled.");
        }

        RegisterEventHandler<EventItemEquip>(OnItemEquip);

        RegisterListener<Listeners.OnClientAuthorized>(OnClientAuthorized);
        RegisterListener<Listeners.OnServerPrecacheResources>(OnServerPrecacheResources);
        RegisterListener<Listeners.OnEntityCreated>(OnEntityCreated);

		if(hotReload)
		{
			foreach(var player in Utilities.GetPlayers())
			{
				if(player.AuthorizedSteamID != null)
					OnClientAuthorized(player.Slot, player.AuthorizedSteamID);
			}
		}

        /*AddCommand("css_fullupdate", "full update test", (player, info) =>
        {
            if (player == null || !player.IsValid)
                return;

            // note that this is not preferred at all as these values are not needed to be recreated each time
            INetworkServerService networkServerService = new INetworkServerService();
            INetworkGameServer networkGameServer = networkServerService.GetIGameServer();

            networkGameServer.ForceFullUpdate(player.Slot);
        });*/
    }

    [ConsoleCommand("css_cw")]
    public void CW_Menu(CCSPlayerController? player, CommandInfo commandInfo)
    {
        if(player == null)
            return;

        CenterHtmlMenu menu = new CenterHtmlMenu("Выберите оружие:", this);
        foreach (var weapons in Config.CW_List)
        {
            menu.AddMenuOption(weapons.Key.Replace("weapon_", "").ToUpper(), (player, _) => { CW_ModelMenu(player, weapons.Value, weapons.Key); } );
        }
        menu.Open(player);
    }

    public void CW_ModelMenu(CCSPlayerController player, Dictionary<string, string> models, string weapon_name)
    {
        CenterHtmlMenu menu = new CenterHtmlMenu("Выберите скин:", this);
        menu.AddMenuOption("Отключить скин", (player, _) => 
        { 
            CW_SetModel(player, weapon_name, "", "", true);
            player.PrintToChat($"Модель успешно отключена!");
        });
        foreach (var weapons in models)
        {
            bool not_access = true;
            if(Config.FreeSkinsAll) not_access = false;
            else
            {
                if(accessWeapons[player.Slot].Count(x => x == ';') > 0)
                {
                    foreach(var WeaponModelName in accessWeapons[player.Slot].Split(";"))
                    {
                        Console.WriteLine($"{WeaponModelName} | {accessWeapons[player.Slot]}");
                        if(WeaponModelName.Equals(weapons.Key, StringComparison.Ordinal))
                            not_access = false;
                    }
                }
                else
                {
                    if(accessWeapons[player.Slot].Equals(weapons.Key, StringComparison.Ordinal))
                        not_access = false;
                }
            }

            menu.AddMenuOption(weapons.Key, (player, _) => 
            { 
                CW_SetModel(player, weapon_name, weapons.Key, weapons.Value, false);
                player.PrintToChat($"Модель {weapons.Key} успешно установлена!");
            }, not_access);
        }
        menu.Open(player);
    }

    public void CW_SetModel(CCSPlayerController player, string weapon_name, string model_name, string model_path, bool remove)
    {
        if (player.PawnIsAlive)
        {
            CBasePlayerWeapon? weapon = player.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;

            if (weapon != null && weapon.DesignerName.Contains(weapon_name))
            {
                if(remove) Weapon.ResetWeapon(player, weapon, true);
                else Weapon.UpdateModel(player, weapon, model_path, true);
            }
        }

        WeaponInfo[player.Slot].Remove(weapon_name);

        if(!remove) WeaponInfo[player.Slot].Add(weapon_name, model_name);

        ulong steamid = player.AuthorizedSteamID!.SteamId64;
        Task.Run(async () => 
        {
            try
            {
                await using (var connection = new MySqlConnection(dbConnectionString))
                {
                    await connection.OpenAsync();
                    await connection.QueryAsync($"UPDATE `custom_weapons` SET `{weapon_name}` = @WeaponModelName WHERE `auth` = @Auth;", new
                    {
                        WeaponModelName = model_name,
                        Auth = steamid
                    });
                }
            }
            catch (Exception ex)
            {
                Logger.LogError("{CW_SetModel} Failed get info in database | " + ex.Message);
                Logger.LogDebug(ex.Message);
                throw new Exception("[CW] Failed get info in database! | " + ex.Message);
            }
        });
    }

    [ConsoleCommand("css_cw_give")]
    [CommandHelper(minArgs: 2, usage: "<userid/name/steamid> <model_name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    [RequiresPermissions("@css/root")]
    public void CW_GiveWeapon(CCSPlayerController? player, CommandInfo commandInfo)
    {
        if(!commandInfo.GetArg(1).StartsWith("#"))
        {
            var targets = commandInfo.GetArgTargetResult(1);

            foreach(var target in targets)
            {
                if(target == null) continue;
                string modelname = commandInfo.GetArg(2);
                if(accessWeapons[target.Slot].Length > 0) accessWeapons[target.Slot] += modelname + ";";
                else accessWeapons[target.Slot] = modelname + ";";

                ulong steamid = target.AuthorizedSteamID!.SteamId64;
                
                Task.Run(async () => 
                {
                    try
                    {
                        await using (var connection = new MySqlConnection(dbConnectionString))
                        {
                            await connection.OpenAsync();
                            var data = await connection.QueryFirstOrDefaultAsync<string?>("SELECT `access_weapons` FROM `custom_weapons` WHERE `auth` = @Auth;", new
                            {
                                Auth = steamid
                            });

                            await connection.QueryAsync("UPDATE `custom_weapons` SET `access_weapons` = @AccessList WHERE `auth` = @Auth;", new
                            {
                                AccessList = accessWeapons[target.Slot],
                                Auth = steamid
                            });
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError("{CW_GiveWeapon} Failed get info in database | " + ex.Message);
                        Logger.LogDebug(ex.Message);
                        throw new Exception("[CW] Failed get info in database! | " + ex.Message);
                    }
                });
            }
        }
        else
        {
            ulong steamid = Convert.ToUInt64(commandInfo.GetArg(1).Replace("#", ""));
            string modelname = commandInfo.GetArg(2);
            var target = Utilities.GetPlayers().FirstOrDefault(x => !x.IsBot && !x.IsHLTV && x.AuthorizedSteamID!.SteamId64 == steamid);
            if(target != null)
            {
                if(accessWeapons[target.Slot].Length > 0) accessWeapons[target.Slot] += modelname + ";";
                else accessWeapons[target.Slot] = modelname + ";";
            }
            
            Task.Run(async () => 
            {
                try
                {
                    await using (var connection = new MySqlConnection(dbConnectionString))
                    {
                        await connection.OpenAsync();
                        var data = await connection.QueryFirstOrDefaultAsync<string?>("SELECT `access_weapons` FROM `custom_weapons` WHERE `auth` = @Auth;", new
                        {
                            Auth = steamid
                        });

                        string model = "";
                        if(data != null)
                        {
                            model = data + modelname + ";";
                        }
                        else
                        {
                            model = modelname + ";";
                        }

                        await connection.QueryAsync("UPDATE `custom_weapons` SET `access_weapons` = @AccessList WHERE `auth` = @Auth;", new
                        {
                            AccessList = model,
                            Auth = steamid
                        });
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError("{CW_GiveWeapon} Failed get info in database | " + ex.Message);
                    Logger.LogDebug(ex.Message);
                    throw new Exception("[CW] Failed get info in database! | " + ex.Message);
                }
            });
        }
        
        commandInfo.ReplyToCommand("Model gived!");
    }

    [ConsoleCommand("css_cw_remove")]
    [CommandHelper(minArgs: 2, usage: "<userid/name/#steamid> <model_name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    [RequiresPermissions("@css/root")]
    public void CW_RemoveWeapon(CCSPlayerController? player, CommandInfo commandInfo)
    {
        if(!commandInfo.GetArg(1).StartsWith("#"))
        {
            var targets = commandInfo.GetArgTargetResult(1);

            foreach(var target in targets)
            {
                if(target == null) continue;
                string modelname = commandInfo.GetArg(2);

                accessWeapons[target.Slot] = accessWeapons[target.Slot].Replace(modelname + ";", "");

                ulong steamid = target.AuthorizedSteamID!.SteamId64;
                
                Task.Run(async () => 
                {
                    try
                    {
                        await using (var connection = new MySqlConnection(dbConnectionString))
                        {
                            await connection.OpenAsync();
                            var data = await connection.QueryFirstOrDefaultAsync<string?>("SELECT `access_weapons` FROM `custom_weapons` WHERE `auth` = @Auth;", new
                            {
                                Auth = steamid
                            });

                            await connection.QueryAsync("UPDATE `custom_weapons` SET `access_weapons` = @AccessList WHERE `auth` = @Auth;", new
                            {
                                AccessList = accessWeapons[target.Slot],
                                Auth = steamid
                            });
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError("{CW_GiveWeapon} Failed get info in database | " + ex.Message);
                        Logger.LogDebug(ex.Message);
                        throw new Exception("[CW] Failed get info in database! | " + ex.Message);
                    }
                });
            }
        }
        else
        {
            ulong steamid = Convert.ToUInt64(commandInfo.GetArg(1).Replace("#", ""));
            string modelname = commandInfo.GetArg(2);
            var target = Utilities.GetPlayers().FirstOrDefault(x => !x.IsBot && !x.IsHLTV && x.AuthorizedSteamID!.SteamId64 == steamid);
            if(target != null)
            {
                accessWeapons[target.Slot] = accessWeapons[target.Slot].Replace(modelname + ";", "");
            }
            
            Task.Run(async () => 
            {
                try
                {
                    await using (var connection = new MySqlConnection(dbConnectionString))
                    {
                        await connection.OpenAsync();
                        var data = await connection.QueryFirstOrDefaultAsync<string?>("SELECT `access_weapons` FROM `custom_weapons` WHERE `auth` = @Auth;", new
                        {
                            Auth = steamid
                        });

                        string model = "";
                        if(data != null)
                        {
                            model = data.Replace(modelname + ";", "");
                        }

                        await connection.QueryAsync("UPDATE `custom_weapons` SET `access_weapons` = @AccessList WHERE `auth` = @Auth;", new
                        {
                            AccessList = model,
                            Auth = steamid
                        });
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError("{CW_GiveWeapon} Failed get info in database | " + ex.Message);
                    Logger.LogDebug(ex.Message);
                    throw new Exception("[CW] Failed get info in database! | " + ex.Message);
                }
            });
        }
        
        commandInfo.ReplyToCommand("Model removed!");
    }

    public void OnClientAuthorized(int playerSlot, SteamID steamID)
	{
		CCSPlayerController? player = Utilities.GetPlayerFromSlot(playerSlot);

		if (player == null || !player.IsValid || player.IsBot || player.IsHLTV)
			return;

        WeaponInfo[player.Slot] = new Dictionary<string, string>();
        accessWeapons[player.Slot] = "";
        ulong steamid = steamID.SteamId64;
        Task.Run(async () => 
        {
            try
            {
                await using (var connection = new MySqlConnection(dbConnectionString))
                {
                    await connection.OpenAsync();
                    IEnumerable<dynamic> data = await connection.QueryAsync("SELECT * FROM `custom_weapons` WHERE `auth` = @Auth;", new
                    {
                        Auth = steamid
                    });

                    if(data != null && data.Count() > 0)
                    {
                        foreach(var row in data)
                        {
                            foreach (var property in row)
                            {
                                string key = property.Key;
                                var value = property.Value;
                                if(key.StartsWith("weapon_") && value != null && value!.Length > 0)
                                {
                                    WeaponInfo[player.Slot].Add(key, value);
                                    //Console.WriteLine($"Key {key} | Value {value}");
                                }

                                if(key.Equals("access_weapons") && value != null && value!.Length > 0)
                                {
                                    accessWeapons[player.Slot] = value!;
                                }
                            }
                        }
                    }
                    else
                    {
                        await connection.QueryAsync("INSERT INTO `custom_weapons` (`auth`, `access_weapons`) VALUES (@Auth, '');", new
                        {
                            Auth = steamid
                        });
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogError("{OnClientAuthorized} Failed get info in database | " + ex.Message);
                Logger.LogDebug(ex.Message);
                throw new Exception("[CW] Failed get info in database! | " + ex.Message);
            }
        });
    }

    public void OnServerPrecacheResources(ResourceManifest manifest)
    {
        foreach (var weapons in Config.CW_List)
        {
            foreach(var weapon_paths in weapons.Value)
            {
                manifest.AddResource(weapon_paths.Value);
            }
        }
    }

    public void OnEntityCreated(CEntityInstance entity)
    {
        if (!entity.DesignerName.StartsWith("weapon_"))
        {
            return;
        }

        CBasePlayerWeapon weapon = entity.As<CBasePlayerWeapon>();

        Server.NextWorldUpdate(() =>
        {
            if (weapon == null || weapon.Handle == IntPtr.Zero || weapon.OriginalOwnerXuidLow == 0)
                return;

            CCSPlayerController? player = Utilities.GetPlayerFromSteamId(weapon.OriginalOwnerXuidLow);

            if (player == null)
                return;

            if(!WeaponInfo[player.Slot].TryGetValue(weapon.DesignerName, out var modelname))
                return;

            if (!Config.CW_List.TryGetValue(weapon.DesignerName, out var value))
                return;

            if(!value.TryGetValue(modelname, out var path))
                return;

            CBasePlayerWeapon? activeweapon = player.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;

            if (activeweapon != null && weapon == activeweapon)
            {
                Weapon.UpdateModel(player, activeweapon, path, true);
            }
            else
            {
                Weapon.UpdateModel(player, weapon, path, false);
            }
        });
    }

    public HookResult OnItemEquip(EventItemEquip @event, GameEventInfo info)
    {
        CCSPlayerController? player = @event.Userid;

        if (player == null)
        {
            return HookResult.Continue;
        }

        CBasePlayerWeapon? activeweapon = player.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;

        if (activeweapon == null)
        {
            return HookResult.Continue;
        }

        string globalname = activeweapon.Globalname;

        if (!string.IsNullOrEmpty(globalname))
        {
            Weapon.SetViewModel(player, globalname.Split(',')[1]);
        }

        return HookResult.Continue;
    }
    public void OnConfigParsed(CustomWeapons_Config config)
	{
        Config = config;
        if (config.DatabaseHost.Length < 1 || config.DatabaseName.Length < 1 || config.DatabaseUser.Length < 1)
			return;

        MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder
		{
			Server = config.DatabaseHost,
			Database = config.DatabaseName,
			UserID = config.DatabaseUser,
			Password = config.DatabasePassword,
			Port = (uint)config.DatabasePort,
		};

        dbConnectionString = builder.ConnectionString;

        Task.Run(async () => 
        {
            try
            {
                await using (var connection = new MySqlConnection(dbConnectionString))
                {
                    connection.Open();

                    string sql = @"CREATE TABLE IF NOT EXISTS `custom_weapons` (
                                    `id` int NOT NULL PRIMARY KEY AUTO_INCREMENT,
                                    `auth` VARCHAR(32) NOT NULL,
                                    `access_weapons` TEXT,
                                    `weapon_m4a1` VARCHAR(64),
                                    `weapon_m4a1_silencer` VARCHAR(64),
                                    `weapon_famas` VARCHAR(64),
                                    `weapon_aug` VARCHAR(64),
                                    `weapon_ak47` VARCHAR(64),
                                    `weapon_galilar` VARCHAR(64),
                                    `weapon_sg556` VARCHAR(64),
                                    `weapon_scar20` VARCHAR(64),
                                    `weapon_awp` VARCHAR(64),
                                    `weapon_ssg08` VARCHAR(64),
                                    `weapon_g3sg1` VARCHAR(64),
                                    `weapon_mp9` VARCHAR(64),
                                    `weapon_mp7` VARCHAR(64),
                                    `weapon_mp5sd` VARCHAR(64),
                                    `weapon_ump45` VARCHAR(64),
                                    `weapon_p90` VARCHAR(64),
                                    `weapon_bizon` VARCHAR(64),
                                    `weapon_mac10` VARCHAR(64),
                                    `weapon_usp_silencer` VARCHAR(64),
                                    `weapon_hkp2000` VARCHAR(64),
                                    `weapon_glock` VARCHAR(64),
                                    `weapon_elite` VARCHAR(64),
                                    `weapon_p250` VARCHAR(64),
                                    `weapon_fiveseven` VARCHAR(64),
                                    `weapon_cz75a` VARCHAR(64),
                                    `weapon_tec9` VARCHAR(64),
                                    `weapon_revolver` VARCHAR(64),
                                    `weapon_deagle` VARCHAR(64),
                                    `weapon_nova` VARCHAR(64),
                                    `weapon_xm1014` VARCHAR(64),
                                    `weapon_mag7` VARCHAR(64),
                                    `weapon_sawedoff` VARCHAR(64),
                                    `weapon_m249` VARCHAR(64),
                                    `weapon_negev` VARCHAR(64),
                                    `weapon_taser` VARCHAR(64),
                                    `weapon_hegrenade` VARCHAR(64),
                                    `weapon_molotov` VARCHAR(64),
                                    `weapon_incgrenade` VARCHAR(64),
                                    `weapon_smokegrenade` VARCHAR(64),
                                    `weapon_flashbang` VARCHAR(64),
                                    `weapon_decoy` VARCHAR(64),
                                    `weapon_knife` VARCHAR(64)
                                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;";

                    await connection.ExecuteAsync(sql);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError("{OnConfigParsed} Unable to connect to database! | " + ex.Message);
                Logger.LogDebug(ex.Message);
                throw new Exception("[CW] Unable to connect to Database! | " + ex.Message);
            }
        });
    }
}

/* Thanks Schwarper! */

public class Weapon 
{
    //static INetworkGameServer networkGameServer = new INetworkServerService().GetIGameServer();
    public static unsafe CBaseViewModel ViewModel(CCSPlayerController player)
    {
        CCSPlayer_ViewModelServices viewModelServices = new(player.PlayerPawn.Value!.ViewModelServices!.Handle);

        nint ptr = viewModelServices.Handle + Schema.GetSchemaOffset("CCSPlayer_ViewModelServices", "m_hViewModel");
        Span<nint> viewModels = MemoryMarshal.CreateSpan(ref ptr, 3);

        CHandle<CBaseViewModel> viewModel = new(viewModels[0]);

        return viewModel.Value!;
    }
    public static unsafe string GetViewModel(CCSPlayerController player)
    {
        return ViewModel(player).VMName;
    }
    public static unsafe void SetViewModel(CCSPlayerController player, string model)
    {
        ViewModel(player).SetModel(model);
        //networkGameServer.ForceFullUpdate(player);
    }
    public static void UpdateModel(CCSPlayerController player, CBasePlayerWeapon weapon, string model, bool update)
    {
        weapon.Globalname = $"{GetViewModel(player)},{model}";
        weapon.SetModel(model);

        if (update)
        {
            SetViewModel(player, model);
        }
    }
    public static void ResetWeapon(CCSPlayerController player, CBasePlayerWeapon weapon, bool update)
    {
        string globalname = weapon.Globalname;

        if (string.IsNullOrEmpty(globalname))
        {
            return;
        }

        string[] globalnamedata = globalname.Split(',');

        weapon.Globalname = string.Empty;
        weapon.SetModel(globalnamedata[0]);

        if (update)
        {
            SetViewModel(player, globalnamedata[0]);
        }
    }
}


extract only code for settings custom model to a weapon