using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using CounterStrikeSharp.API.Core.Plugin.Host;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CS2_SimpleAdmin.Managers;
using MenuManager;
using ZLinq;

namespace CS2_SimpleAdmin;

internal static class Helper
{
    private static readonly string AssemblyName = Assembly.GetExecutingAssembly().GetName().Name ?? "";
    private static readonly string CfgPath = $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/configs/plugins/{AssemblyName}/{AssemblyName}.json";

    private delegate nint CNetworkSystemUpdatePublicIp(nint a1);

    private static CNetworkSystemUpdatePublicIp? _networkSystemUpdatePublicIp;

    public static bool IsDebugBuild
    {
        get
        {
#if DEBUG
            return true;
#else
			return false;
#endif
        }
    }

    public static List<CCSPlayerController> GetPlayerFromName(string name)
    {
        return GetValidPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase));
    }

    public static CCSPlayerController? GetPlayerFromSteamid64(ulong steamid)
    {
        return GetValidPlayers().FirstOrDefault(x => x.SteamID == steamid);
    }

    public static List<CCSPlayerController> GetPlayerFromIp(string ipAddress)
    {
        return CS2_SimpleAdmin.CachedPlayers.FindAll(x => x.IpAddress != null && x.IpAddress.Split(":")[0].Equals(ipAddress));
    }

    public static List<CCSPlayerController> GetValidPlayers()
    {
        return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.Connected).ToList();
    }
    
    public static List<CCSPlayerController> GetValidPlayersWithBots()
    {
        return CS2_SimpleAdmin.CachedPlayers.Concat(CS2_SimpleAdmin.BotPlayers).AsValueEnumerable().ToList();
    }

    // public static bool IsValidSteamId64(string input)
    // {
    //     const string pattern = @"^\d{17}$";
    //     return Regex.IsMatch(input, pattern);
    // }

    public static bool ValidateSteamId(string input, out SteamID? steamId)
    {
        steamId = null;

        if (string.IsNullOrEmpty(input))
        {
            return false;
        }
        
        if (!SteamID.TryParse(input, out var parsedSteamId)) return false;

        steamId = parsedSteamId;
        return true;
    }

    public static bool IsValidIp(string input)
    {
        const string pattern = @"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
        return Regex.IsMatch(input, pattern);
    }

    public static void GivePlayerFlags(SteamID? steamid, List<string>? flags = null, uint immunity = 0)
    {
        try
        {
            if (steamid == null || (flags == null && immunity == 0))
            {
                return;
            }

            if (flags == null) return;
            foreach (var flag in flags.Where(flag => !string.IsNullOrEmpty(flag)))
            {
                if (flag.StartsWith($"@"))
                {
                    //Console.WriteLine($"Adding permission {flag} to SteamID {steamid}");
                    AdminManager.AddPlayerPermissions(steamid, flag);
                }
                else if (flag.StartsWith($"#"))
                {
                    //Console.WriteLine($"Adding SteamID {steamid} to group {flag}");
                    AdminManager.AddPlayerToGroup(steamid, flag);
                }
            }

            AdminManager.SetPlayerImmunity(steamid, immunity);
        }
        catch
        {
        }
    }

    public static void KickPlayer(int userId, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0)
    {
        var player = Utilities.GetPlayerFromUserid(userId);

        if (player == null || !player.IsValid || player.IsHLTV)
            return;
        
        if (player.UserId.HasValue && CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
            value.WaitingForKick = true;

        // player.CommitSuicide(true, true);
        player.VoiceFlags = VoiceFlags.Muted;
        var playerPawn = player.PlayerPawn.Value;

        if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
        {
            playerPawn.Freeze();
            playerPawn.Colorize(255, 0, 0);
            
            var weaponServices = playerPawn.WeaponServices;
            if (weaponServices == null)
                return;

            foreach (var _weap in weaponServices.MyWeapons)
            {
                var weapon = _weap.Value;;
                if (weapon == null || !weapon.IsValid)
                    continue;
                if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot"))
                    continue;

                weapon.NextPrimaryAttackTick = Server.TickCount + 999;
                weapon.NextSecondaryAttackTick = Server.TickCount + 999;
                Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick");
                Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick");
            }
        }
        
        if (delay > 0)
        {
            CS2_SimpleAdmin.Instance.AddTimer(delay, () =>
            {
                if (!player.IsValid || player.IsHLTV)
                    return;
                
                // Server.ExecuteCommand($"kickid {player.UserId}");

                playerPawn?.Colorize();
                player.Disconnect(reason);
            });
        }
        else
        {
            // Server.ExecuteCommand($"kickid {player.UserId}");

            playerPawn?.Colorize();
            player.Disconnect(reason); 
        }
        
        if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED)
            Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");

        // if (!string.IsNullOrEmpty(reason))
        // {
        // 	var escapeChars = reason.IndexOfAny([';', '|']);
        //
        // 	if (escapeChars != -1)
        // 	{
        // 		reason = reason[..escapeChars];
        // 	}
        // }
        //
        // Server.ExecuteCommand($"kickid {userId} {reason}");
    }
    
    public static void KickPlayer(CCSPlayerController player, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0)
    {
        if (!player.IsValid || player.IsHLTV)
            return;

        if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out var value))
        {
            if (value.WaitingForKick)
                return;
            
            value.WaitingForKick = true;
        }
        
        player.VoiceFlags = VoiceFlags.Muted;
        var playerPawn = player.PlayerPawn.Value;
        if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE)
        {
            playerPawn.Freeze();
            playerPawn.Colorize(255, 0, 0);

            var weaponServices = playerPawn.WeaponServices;
            if (weaponServices == null)
                return;

            foreach (var _weap in weaponServices.MyWeapons)
            {
                var weapon = _weap.Value;
                ;
                if (weapon == null || !weapon.IsValid)
                    continue;
                if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot"))
                    continue;

                weapon.NextPrimaryAttackTick = Server.TickCount + 999;
                weapon.NextSecondaryAttackTick = Server.TickCount + 999;
                Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick");
                Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick");
            }
        }

        if (delay > 0)
        {
            CS2_SimpleAdmin.Instance.AddTimer(delay, () =>
            {
                if (!player.IsValid || player.IsHLTV)
                    return;
                
                // if (!string.IsNullOrEmpty(reason))
                // {
                // 	var escapeChars = reason.IndexOfAny([';', '|']);
                //
                // 	if (escapeChars != -1)
                // 	{
                // 		reason = reason[..escapeChars];
                // 	}
                // }
                //
                // Server.ExecuteCommand($"kickid {player.UserId}");
                player.Disconnect(reason);
            });
        }
        else
        {
            // Server.ExecuteCommand($"kickid {player.UserId}");

            player.Disconnect(reason);
        }
        
        if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED)
            Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");

        // if (!string.IsNullOrEmpty(reason))
        // {
        // 	var escapeChars = reason.IndexOfAny([';', '|']);
        //
        // 	if (escapeChars != -1)
        // 	{
        // 		reason = reason[..escapeChars];
        // 	}
        // }
        //
        // Server.ExecuteCommand($"kickid {userId} {reason}");
    }

    public static int ParsePenaltyTime(string time)
    {
        time = time.ToLower();
        if (string.IsNullOrWhiteSpace(time) || !time.Any(char.IsDigit))
        {
            // CS2_SimpleAdmin._logger?.LogError("Time string cannot be null or empty.");
            return -1;
        }

        if (time.Equals($"0"))
            return 0;

        var timeUnits = new Dictionary<string, int>
        {
            { "m", 1 },            // Minute
            { "h", 60 },           // Hour
            { "d", 1440 },         // Day (24 * 60)
            { "w", 10080 },        // Week (7 * 24 * 60)
            { "mo", 43200 },        // Month (30 * 24 * 60)
            { "y", 525600 }         // Year (365 * 24 * 60)
        };

        // Check if the input is purely numeric (e.g., "10" for 10 minutes)
        if (int.TryParse(time, out var numericMinutes))
        {
            return numericMinutes;
        }
        
        int totalMinutes = 0;
        
        var regex = new Regex(@"(\d+)([a-z]+)");
        var matches = regex.Matches(time);

        foreach (Match match in matches)
        {
            var value = int.Parse(match.Groups[1].Value); // Numeric part
            var unit = match.Groups[2].Value;            // Unit part

            if (timeUnits.TryGetValue(unit, out var minutesPerUnit))
            {
                totalMinutes += value * minutesPerUnit;
            }
            else
            {
                throw new ArgumentException($"Invalid time unit '{unit}' in time string.", nameof(time));
            }
        }
        
        return totalMinutes > 0 ? totalMinutes : -1;
    }

    public static void PrintToCenterAll(string message)
    {
        Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false }).ToList().ForEach(controller =>
        {
            controller.PrintToCenter(message);
        });
    }

    internal static void HandleVotes(CCSPlayerController player, ChatMenuOption option)
    {
        if (!CS2_SimpleAdmin.VoteInProgress)
            return;

        option.Disabled = true;
        CS2_SimpleAdmin.VoteAnswers[option.Text]++;
    }

    internal static void LogCommand(CCSPlayerController? caller, CommandInfo command)
    {
        if (CS2_SimpleAdmin._localizer == null)
            return;

        var playerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer["sa_console"];
        var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer["sa_unknown"];

        CS2_SimpleAdmin.Instance.Logger.LogInformation($"{CS2_SimpleAdmin._localizer[
            "sa_discord_log_command",
            playerName, command.GetCommandString]}".Replace("HOSTNAME", hostname).Replace("**", ""));
        
        SendDiscordLogMessage(caller, command, CS2_SimpleAdmin._localizer);
    }

    internal static void LogCommand(CCSPlayerController? caller, string command)
    {
        if (CS2_SimpleAdmin._localizer == null)
            return;

        var playerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer["sa_console"];
        var hostnameCvar = ConVar.Find("hostname");

        var hostname = hostnameCvar?.StringValue ?? CS2_SimpleAdmin._localizer["sa_unknown"];

        CS2_SimpleAdmin.Instance.Logger.LogInformation($"{CS2_SimpleAdmin._localizer["sa_discord_log_command",
            playerName, command]}".Replace("HOSTNAME", hostname).Replace("**", ""));

        SendDiscordLogMessage(caller, command, CS2_SimpleAdmin._localizer);
    }

    /*public static IEnumerable<Embed> GenerateEmbedsDiscord(string title, string description, string thumbnailUrl, Color color, string[] fieldNames, string[] fieldValues, bool[] inlineFlags)
	{
		var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
		var address = $"{ConVar.Find("ip")?.StringValue}:{ConVar.Find("hostport")!.GetPrimitiveValue<int>()}";

		description = description.Replace("{hostname}", hostname);
		description = description.Replace("{address}", address);

		var embed = new EmbedBuilder
		{
			Title = title,
			Description = description,
			ThumbnailUrl = thumbnailUrl,
			Color = color,
		};

		for (var i = 0; i < fieldNames.Length; i++)
		{
			fieldValues[i] = fieldValues[i].Replace("{hostname}", hostname ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown");
			fieldValues[i] = fieldValues[i].Replace("{address}", address ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown");

			embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]);

			if ((i + 1) % 2 == 0 && i < fieldNames.Length - 1)
			{
				embed.AddField("\u200b", "\u200b");
			}
		}

		return new List<Embed> { embed.Build() };
	}*/

    private static void SendDiscordLogMessage(CCSPlayerController? caller, CommandInfo command, IStringLocalizer? localizer)
    {
        if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || localizer == null) return;

        var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : "<https://steamcommunity.com/profiles/0>";
        var callerName = caller != null ? caller.PlayerName : CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
        _ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString]));
    }

    private static void SendDiscordLogMessage(CCSPlayerController? caller, string command, IStringLocalizer? localizer)
    {
        if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || localizer == null) return;

        var communityUrl = caller != null ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">" : "<https://steamcommunity.com/profiles/0>";
        var callerName = caller != null ? caller.PlayerName : CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
        _ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command]));
    }

    public static void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
    {
        string[] publishActions = ["ban", "gag", "silence", "mute"];

        if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
        if (CS2_SimpleAdmin._localizer == null) return;

        if (string.IsNullOrWhiteSpace(callerName))
            callerName = CS2_SimpleAdmin._localizer["sa_console"];

        var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();

        if (!dontPublish && publishActions.Any(messageKey.Contains))
        {
            CS2_SimpleAdmin.SimpleAdminApi?.OnAdminShowActivityEvent(messageKey, callerName, dontPublish, messageArgs);
        }

        // // Replace placeholder based on showActivityType
        // for (var i = 0; i < formattedMessageArgs.Length; i++)
        // {
        // 	var arg = formattedMessageArgs[i]; // Convert argument to string if not null
        // 	// Replace "CALLER" placeholder in the argument string
        // 	formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
        // 	{
        // 		1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
        // 		2 => arg.Replace("CALLER", callerName ?? "Console"),
        // 		_ => arg
        // 	};
        // }
        var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });

        if (!validPlayers.Any())
            return;

        if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
        {
            validPlayers = validPlayers.Where(c =>
                AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
                AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
        }

        foreach (var controller in validPlayers.ToList())
        {
            var currentMessageArgs = (string[])formattedMessageArgs.Clone();

            // Replace "CALLER" placeholder based on showActivityType and whether the recipient is an admin
            for (var i = 0; i < currentMessageArgs.Length; i++)
            {
                var arg = currentMessageArgs[i];
                currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
                {
                    1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
                    _ => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
                };
            }

            // Send the localized message to each player
            controller.SendLocalizedMessage(CS2_SimpleAdmin._localizer, messageKey, currentMessageArgs.Cast<object>().ToArray());
        }
    }

    /// <summary>
    /// Shows admin activity with a custom translated message (for modules with their own localizer).
    /// </summary>
    public static void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false)
    {
        if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
        if (CS2_SimpleAdmin._localizer == null) return;

        if (string.IsNullOrWhiteSpace(callerName))
            callerName = CS2_SimpleAdmin._localizer["sa_console"];

        var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });

        if (!validPlayers.Any())
            return;

        if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
        {
            validPlayers = validPlayers.Where(c =>
                AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
                AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
        }

        foreach (var controller in validPlayers.ToList())
        {
            // Replace "CALLER" placeholder based on showActivityType
            var message = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
            {
                1 => translatedMessage.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
                _ => translatedMessage.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
            };

            // Send the pre-translated message to the player
            controller.PrintToChat(message);
        }
    }

    /// <summary>
    /// Shows admin activity using module's localizer for per-player language support.
    /// Each player receives the message in their configured language using SendLocalizedMessage.
    /// </summary>
    public static void ShowAdminActivityLocalized(IStringLocalizer moduleLocalizer, string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
    {
        if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) return;
        if (CS2_SimpleAdmin._localizer == null) return;

        if (string.IsNullOrWhiteSpace(callerName))
            callerName = CS2_SimpleAdmin._localizer["sa_console"];

        var formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray();

        var validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false });

        if (!validPlayers.Any())
            return;

        if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3)
        {
            validPlayers = validPlayers.Where(c =>
                AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") ||
                AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban"));
        }

        foreach (var controller in validPlayers.ToList())
        {
            var currentMessageArgs = (string[])formattedMessageArgs.Clone();

            // Replace "CALLER" placeholder based on showActivityType
            for (var i = 0; i < currentMessageArgs.Length; i++)
            {
                var arg = currentMessageArgs[i];
                currentMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
                {
                    1 => arg.Replace("CALLER", AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban") ? callerName : CS2_SimpleAdmin._localizer["sa_admin"]),
                    _ => arg.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]),
                };
            }

            // Send the localized message to each player using their language
            controller.SendLocalizedMessage(moduleLocalizer, messageKey, currentMessageArgs.Cast<object>().ToArray());
        }
    }

    public static void DisplayCenterMessage(
        CCSPlayerController player, 
        string messageKey, 
        string? callerName = null, 
        params object[] messageArgs)
    {
        if (CS2_SimpleAdmin._localizer == null) return;

        // Determine the localized message key
        var localizedMessageKey = $"{messageKey}";

        var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();

        // Replace placeholder based on showActivityType
        for (var i = 0; i < formattedMessageArgs.Length; i++)
        {
            var arg = formattedMessageArgs[i]; // Convert argument to string if not null
            // Replace "CALLER" placeholder in the argument string
            formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
            {
                1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
                2 => arg.Replace("CALLER", callerName ?? "Console"),
                _ => arg
            };
        }

        // Print the localized message to the center of the screen for the player
        using (new WithTemporaryCulture(player.GetLanguage()))
        {
            player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
        }
    }

    private static string ConvertMinutesToTime(int minutes)
    {
        var time = TimeSpan.FromMinutes(minutes);

        return time.Days > 0 ? $"{time.Days}d {time.Hours}h {time.Minutes}m" : time.Hours > 0 ? $"{time.Hours}h {time.Minutes}m" : $"{time.Minutes}m";
    }

    public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, CCSPlayerController? target, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer)
    {
        if (localizer == null) return;

        var penaltySetting = penalty switch
        {
            PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings,
            PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyMuteSettings,
            PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyGagSettings,
            PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltySilenceSettings,
            PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyWarnSettings,
            _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
        };

        var webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value;

        if (string.IsNullOrEmpty(webhookUrl)) return;

        const string defaultCommunityUrl = "<https://steamcommunity.com/profiles/0>";
        var callerCommunityUrl = caller != null ? $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
        var targetCommunityUrl = target != null ? $"<{new SteamID(target.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;

        var callerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
        var targetName = target?.PlayerName ?? localizer["sa_unknown"];
        var targetSteamId = target != null ? new SteamID(target.SteamID).SteamId64.ToString() : localizer["sa_unknown"];

        var futureTime = Time.ActualDateTime().AddMinutes(duration);
        var futureUnixTimestamp = new DateTimeOffset(futureTime).ToUnixTimeSeconds();

        string time;

        if (penaltySetting.FirstOrDefault(s => s.Name.Equals("Time"))?.Value == "{relative}")
            time = duration != 0 ? $"<t:{futureUnixTimestamp}:R>" : localizer["sa_permanent"];
        else
            time = duration != 0 ? ConvertMinutesToTime(duration) : localizer["sa_permanent"];
            
        string[] fieldNames = [
            localizer["sa_player"],
            localizer["sa_steamid"],
            localizer["sa_duration"],
            localizer["sa_reason"],
            localizer["sa_admin"]];
        string[] fieldValues =
        [
            $"[{targetName}]({targetCommunityUrl})", $"||{targetSteamId}||", time, reason,
            $"[{callerName}]({callerCommunityUrl})"
        ];
        
        bool[] inlineFlags = [true, true, true, false, false];
        var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"];
        var colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value;
        var colorHex = string.IsNullOrWhiteSpace(colorValue) ? "#FFFFFF" : colorValue.Trim();

        var embed = new Embed
        {
            Color = DiscordManager.ColorFromHex(colorHex),
            Title = penalty switch
            {
                PenaltyType.Ban => localizer["sa_discord_penalty_ban"],
                PenaltyType.Mute => localizer["sa_discord_penalty_mute"],
                PenaltyType.Gag => localizer["sa_discord_penalty_gag"],
                PenaltyType.Silence => localizer["sa_discord_penalty_silence"],
                PenaltyType.Warn => localizer["sa_discord_penalty_warn"],
                _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
            },
            Description = $"{hostname}",
            ThumbnailUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ThumbnailUrl"))?.Value,
            ImageUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ImageUrl"))?.Value,
            Footer = new Footer
            {
                Text = penaltySetting.FirstOrDefault(s => s.Name.Equals("Footer"))?.Value
            },
            
            Timestamp = Time.ActualDateTime().ToUniversalTime().ToString("o"),
        };

        for (var i = 0; i < fieldNames.Length; i++)
        {
            embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]);
        }

        Task.Run(async () =>
        {
            try
            {
                await new DiscordManager(webhookUrl).SendEmbedAsync(embed);
            }
            catch (Exception ex)
            {
                // Log or handle the exception
                CS2_SimpleAdmin._logger?.LogError("Unable to send discord webhook: {exception}", ex.Message);
            }
        });
    }
    
    public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, string steamId, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer)
    {
        if (localizer == null) return;

        var penaltySetting = penalty switch
        {
            PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings,
            PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyMuteSettings,
            PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyGagSettings,
            PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltySilenceSettings,
            PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyWarnSettings,
            _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
        };

        var webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value;

        if (string.IsNullOrEmpty(webhookUrl)) return;
        const string defaultCommunityUrl = "<https://steamcommunity.com/profiles/0>";
        var callerCommunityUrl = caller != null ? $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>" : defaultCommunityUrl;
        var targetCommunityUrl = $"<{new SteamID(ulong.Parse(steamId)).ToCommunityUrl()}>";

        var callerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console";
        var targetName = steamId;
        var targetSteamId = steamId;

        var futureTime = Time.ActualDateTime().AddMinutes(duration);
        var futureUnixTimestamp = new DateTimeOffset(futureTime).ToUnixTimeSeconds();

        string time;

        if (penaltySetting.FirstOrDefault(s => s.Name.Equals("Time"))?.Value == "{relative}")
            time = duration != 0 ? $"<t:{futureUnixTimestamp}:R>" : localizer["sa_permanent"];
        else
            time = duration != 0 ? ConvertMinutesToTime(duration) : localizer["sa_permanent"];
            
        string[] fieldNames = [
            localizer["sa_player"],
            localizer["sa_steamid"],
            localizer["sa_duration"],
            localizer["sa_reason"],
            localizer["sa_admin"]];
        string[] fieldValues =
        [
            $"[{targetName}]({targetCommunityUrl})", $"||{targetSteamId}||", time, reason,
            $"[{callerName}]({callerCommunityUrl})"
        ];
        
        bool[] inlineFlags = [true, true, true, false, false];
        var hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"];
        var colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value;
        var colorHex = string.IsNullOrWhiteSpace(colorValue) ? "#FFFFFF" : colorValue.Trim();

        var embed = new Embed
        {
            Color = DiscordManager.ColorFromHex(colorHex),
            Title = penalty switch
            {
                PenaltyType.Ban => localizer["sa_discord_penalty_ban"],
                PenaltyType.Mute => localizer["sa_discord_penalty_mute"],
                PenaltyType.Gag => localizer["sa_discord_penalty_gag"],
                PenaltyType.Silence => localizer["sa_discord_penalty_silence"],
                PenaltyType.Warn => localizer["sa_discord_penalty_warn"],
                _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null)
            },
            Description = $"{hostname}",
            ThumbnailUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ThumbnailUrl"))?.Value,
            ImageUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ImageUrl"))?.Value,
            Footer = new Footer
            {
                Text = penaltySetting.FirstOrDefault(s => s.Name.Equals("Footer"))?.Value
            },
            
            Timestamp = Time.ActualDateTime().ToUniversalTime().ToString("o"),
        };

        for (var i = 0; i < fieldNames.Length; i++)
        {
            embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]);
        }

        Task.Run(async () =>
        {
            try
            {
                await new DiscordManager(webhookUrl).SendEmbedAsync(embed);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        });
    }
    
    private static string GenerateMessageDiscord(string message)
    {
        var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
        var address = $"{ConVar.Find("ip")?.StringValue}:{ConVar.Find("hostport")!.GetPrimitiveValue<int>()}";

        message = message.Replace("HOSTNAME", hostname);
        message = message.Replace("ADDRESS", address);

        return message;
    }

    public static string[] SeparateLines(string message)
    {
        return message.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);
    }

    public static string CenterMessage(string message) =>
        $"⠀⠀⠀⠀⠀⠀⠀⠀{message}⠀⠀⠀⠀⠀⠀⠀⠀";

    public static string GetServerIp()
    {
        var networkSystem = NativeAPI.GetValveInterface(0, "NetworkSystemVersion001");

        unsafe
        {
            if (_networkSystemUpdatePublicIp == null)
            {
                var funcPtr = *(nint*)(*(nint*)(networkSystem) + 256);
                _networkSystemUpdatePublicIp = Marshal.GetDelegateForFunctionPointer<CNetworkSystemUpdatePublicIp>(funcPtr);
            }
            /*
			struct netadr_t
			{
			   uint32_t type
			   uint8_t ip[4]
			   uint16_t port
			}
			*/
            // + 4 to skip type, because the size of uint32_t is 4 bytes
            var ipBytes = (byte*)(_networkSystemUpdatePublicIp(networkSystem) + 4);
            // port is always 0, use the one from convar "hostport"
            return $"{ipBytes[0]}.{ipBytes[1]}.{ipBytes[2]}.{ipBytes[3]}";
        }
    }

    public static void UpdateConfig(BasePluginConfig config)
    {
        // get newest config version
        var configType = config.GetType();
        var newCfgVersion = ((BasePluginConfig)Activator.CreateInstance(configType)!).Version;

        // loaded config is up to date
        if (config.Version == newCfgVersion)
            return;

        // Load existing JSON file and update version property
        if (!File.Exists(CfgPath))
            return;

        var json = File.ReadAllText(CfgPath);
        var node = JsonNode.Parse(json);

        if (node != null)
        {
            node["Version"] = newCfgVersion;
            var updatedJsonContent = node.ToJsonString(new JsonSerializerOptions
            {
                WriteIndented = true,
                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            });
            
            File.WriteAllText(CfgPath, updatedJsonContent);
        }
    }

    public static void TryLogCommandOnDiscord(CCSPlayerController? caller, string commandString)
    {
        if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || CS2_SimpleAdmin._localizer == null)
            return;

        if (caller != null && !caller.IsValid)
            caller = null;

        var callerName = caller == null ? CS2_SimpleAdmin._localizer["sa_console"] : caller.PlayerName;
        var communityUrl = caller != null
            ? "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">"
            : "<https://steamcommunity.com/profiles/0>";
        _ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(
            CS2_SimpleAdmin._localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})",
                commandString]));
    }

    #pragma warning disable CS8604
    public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null, Action<CCSPlayerController>? resetAction = null)
    {
        if (CS2_SimpleAdmin.MenuApi == null)
        {
            return null;
        }

        var menuType = CS2_SimpleAdmin.Instance.Config.MenuConfigs.MenuType.ToLower();
        var menu = menuType switch
        {
            _ when menuType.Equals("selectable", StringComparison.CurrentCultureIgnoreCase) =>
                CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction),

            _ when menuType.Equals("dynamic", StringComparison.CurrentCultureIgnoreCase) =>
                CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ButtonMenu, backAction, resetAction),

            _ when menuType.Equals("center", StringComparison.CurrentCultureIgnoreCase) =>
                CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.CenterMenu, backAction, resetAction),

            _ when menuType.Equals("chat", StringComparison.CurrentCultureIgnoreCase) =>
                CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ChatMenu, backAction, resetAction),

            _ when menuType.Equals("console", StringComparison.CurrentCultureIgnoreCase) =>
                CS2_SimpleAdmin.MenuApi.GetMenuForcetype(title, MenuType.ConsoleMenu, backAction, resetAction),

            _ => CS2_SimpleAdmin.MenuApi.GetMenu(title, backAction, resetAction)
        };

        return menu;
    }
    #pragma warning restore CS8604

    internal static IPluginManager? GetPluginManager()
    {
        // Access the singleton instance of Application
        var applicationInstance = Application.Instance;

        // Use Reflection to access the private _pluginManager field
        var pluginManagerField = typeof(Application).GetField("_pluginManager", BindingFlags.NonPublic | BindingFlags.Instance);
        var pluginManager = pluginManagerField?.GetValue(applicationInstance) as IPluginManager;
        
        return pluginManager;
    }
    
}

public static class PluginInfo
{
    internal static async Task CheckVersion(string localVersion, ILogger logger)
    {
        ShowAd(localVersion);
        const string versionUrl = "https://raw.githubusercontent.com/daffyyyy/CS2-SimpleAdmin/main/CS2-SimpleAdmin/VERSION";
        var client = CS2_SimpleAdmin.HttpClient;
        client.Timeout = TimeSpan.FromSeconds(3);
        
        try
        {
            var response = await client.GetAsync(versionUrl);

            if (response.IsSuccessStatusCode)
            {
                var remoteVersion = await response.Content.ReadAsStringAsync();
                remoteVersion = remoteVersion.Trim();

                var comparisonResult = string.CompareOrdinal(localVersion, remoteVersion);

                switch (comparisonResult)
                {
                    case < 0:
                        logger.LogWarning("Plugin is outdated! Check https://github.com/daffyyyy/CS2-SimpleAdmin");
                        break;
                    case > 0:
                        logger.LogInformation("Probably dev version detected");
                        break;
                    default:
                        logger.LogInformation("Plugin is up to date");
                        break;
                }
            }
            else
            {
                logger.LogWarning($"Failed to check version. Status Code: {response.StatusCode}");
            }
        }
        catch (HttpRequestException ex)
        {
            logger.LogError(ex, "Failed to connect to the version server.");
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "An error occurred while checking version.");
        }
    }

    private static void ShowAd(string moduleVersion)
    {
        Console.WriteLine(" ");
        Console.WriteLine(" _______  ___   __   __  _______  ___      _______  _______  ______   __   __  ___   __    _  ");
        Console.WriteLine("|       ||   | |  |_|  ||       ||   |    |       ||   _   ||      | |  |_|  ||   | |  |  | |");
        Console.WriteLine("|  _____||   | |       ||    _  ||   |    |    ___||  |_|  ||  _    ||       ||   | |   |_| |");
        Console.WriteLine("| |_____ |   | |       ||   |_| ||   |    |   |___ |       || | |   ||       ||   | |       |");
        Console.WriteLine("|_____  ||   | |       ||    ___||   |___ |    ___||       || |_|   ||       ||   | |  _    |");
        Console.WriteLine(" _____| ||   | | ||_|| ||   |    |       ||   |___ |   _   ||       || ||_|| ||   | | | |   |");
        Console.WriteLine("|_______||___| |_|   |_||___|    |_______||_______||__| |__||______| |_|   |_||___| |_|  |__|");
        Console.WriteLine("				>> Version: " + moduleVersion);
        Console.WriteLine("		>> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin");
        var _m1 = new List<int> { 70,117,99,107,32,121,111,117,32,114,97,118,105,100,44,32,119,104,121,32,121,111,117,32,108,121,105,110,103,32,97,98,111,117,116,32,110,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,63,32,74,117,115,116,32,116,101,108,108,32,116,114,117,116,104,44,32,110,105,99,101,32,116,114,121,32,116,111,32,112,114,111,106,101,99,116,32,116,97,107,101,111,118,101,114 };
        var _m2 = new List<int> { 32,32,58,68,32,40,97,108,108,32,119,104,111,32,116,114,105,101,100,32,116,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,44,32,106,117,115,116,32,119,114,105,116,101,32,109,101,115,115,97,103,101,32,116,111,32,109,101,32,120,68,41 };
        Console.WriteLine("		>> " + new string(_m1.Select(c => (char)c).ToArray()));
        Console.WriteLine("		>> " + new string(_m2.Select(c => (char)c).ToArray()));
        Console.WriteLine(" ");
    }
}

public class SchemaString<TSchemaClass>(TSchemaClass instance, string member)
    : NativeObject(Schema.GetSchemaValue<nint>(instance.Handle, typeof(TSchemaClass).Name, member))
    where TSchemaClass : NativeObject
{
    public unsafe void Set(string str)
    {
        var bytes = GetStringBytes(str);

        for (var i = 0; i < bytes.Length; i++)
        {
            Unsafe.Write((void*)(Handle.ToInt64() + i), bytes[i]);
        }

        Unsafe.Write((void*)(Handle.ToInt64() + bytes.Length), 0);
    }

    private static byte[] GetStringBytes(string str)
    {
        return Encoding.UTF8.GetBytes(str);
    }
}

public static class Time
{
    public static DateTime ActualDateTime()
    {
        if (CS2_SimpleAdmin.Instance.Config.DatabaseConfig.DatabaseType.ToLower().Equals("sqlite"))
            return DateTime.UtcNow;
        
        string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone;
        DateTime utcNow = DateTime.UtcNow;

        try
        {
            TimeZoneInfo timezone = TimeZoneInfo.FindSystemTimeZoneById(timezoneId);
            DateTime userTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, timezone);
            return userTime;
        }
        catch (TimeZoneNotFoundException)
        {
            CS2_SimpleAdmin._logger?.LogWarning($"Time zone '{timezoneId}' not found. Returning UTC time.");
            return utcNow;
        }
        catch (InvalidTimeZoneException)
        {
            CS2_SimpleAdmin._logger?.LogWarning($"Time zone '{timezoneId}' is invalid. Returning UTC time.");
            return utcNow;
        }
    }
}

public static class WeaponHelper
{
    private static readonly Lazy<Dictionary<string, CsItem>> WeaponsEnumCache = new(BuildEnumMemberMap);

    private static Dictionary<string, CsItem> BuildEnumMemberMap()
    {
        var dictionary = new Dictionary<string, CsItem>();

        foreach (var field in typeof(CsItem).GetFields(BindingFlags.Public | BindingFlags.Static))
        {
            var attribute = field.GetCustomAttribute<EnumMemberAttribute>();
            if (attribute?.Value == null) continue;
            if (field.GetValue(null) is not CsItem csItem)
                continue;
            var enumValue = field.GetValue(null);
            
            dictionary.TryAdd(attribute.Value, csItem);
        }

        return dictionary;
    }
    
    public static CsItem? GetEnumFromWeaponName(string weaponName)
    {
        if (WeaponsEnumCache.Value.TryGetValue(weaponName, out var csItem))
        {
            return csItem;
        }

        return null;
    }
    
    public static List<(string EnumMemberValue, CsItem EnumValue)> GetWeaponsByPartialName(string input)
    {
        // Normalize input for case-insensitive comparison
        var normalizedInput = input.ToLowerInvariant();

        // Find all matching weapons based on the input
        var matchingWeapons = WeaponsEnumCache.Value
            .Where(kvp => kvp.Key.Contains(normalizedInput, StringComparison.InvariantCultureIgnoreCase))
            .Select(kvp => (EnumMemberValue: kvp.Key, EnumValue: kvp.Value))
            .ToList();

        // Check for an exact match first
        var exactMatch = matchingWeapons
            .FirstOrDefault(m => m.EnumMemberValue.Equals(input, StringComparison.OrdinalIgnoreCase));

        if (exactMatch.EnumMemberValue != null)
        {
            // Return a list containing only the exact match
            return [exactMatch];
        }

        // If no exact match, get all matches that start with the input
        var filteredWeapons = matchingWeapons
            .Where(m => m.EnumMemberValue.StartsWith(normalizedInput, StringComparison.InvariantCultureIgnoreCase))
            .ToList();

        return filteredWeapons; // Return all relevant matches for the partial input
    }
}

public static class IpHelper
{
    public static uint IpToUint(string ipAddress)
    {
        if (string.IsNullOrWhiteSpace(ipAddress))
            throw new ArgumentException("IP address cannot be null or empty.", nameof(ipAddress));

        if (!System.Net.IPAddress.TryParse(ipAddress, out var ip))
            throw new FormatException($"Invalid IP address format: {ipAddress}");

        // Ensure it's IPv4 (IPv6 will throw)
        if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
            throw new FormatException("Only IPv4 addresses are supported.");

        byte[] bytes = ip.GetAddressBytes();
        if (BitConverter.IsLittleEndian)
            Array.Reverse(bytes); // Ensure big-endian (network order)

        return BitConverter.ToUInt32(bytes, 0);
    }
    
    public static bool TryConvertIpToUint(string ipString, out uint ipUint)
    {
        ipUint = 0;
        if (string.IsNullOrWhiteSpace(ipString))
            return false;

        if (!System.Net.IPAddress.TryParse(ipString, out var ipAddress))
            return false;

        var bytes = ipAddress.GetAddressBytes();
        if (bytes.Length != 4)
            return false;

        ipUint = IpToUint(ipString);
        return true;
    }

    public static string UintToIp(uint ipAddress)
    {
        var bytes = BitConverter.GetBytes(ipAddress).Reverse().ToArray();
        return new System.Net.IPAddress(bytes).ToString();
    }
}