using System;
using System.Collections.Generic;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Rust;
using UnityEngine;
using Random = UnityEngine.Random;
using Time = UnityEngine.Time;

namespace Oxide.Plugins
{
    [Info("Dungeons", "Marte6", "1.0.0")]
    public class Dungeons : RustPlugin
    {
        private ConfigData _configData;
        private Timer _autoSpawnTimer;
        private BaseEntity _currentDungeon;
        private List<BaseEntity> _spawnedEntities = new();
        private Dictionary<DungeonTierConfig, string> _tierNameMap;

        private const string XmasDungeonPrefabPath = "assets/prefabs/missions/portal/xmasportalentry.prefab";
        private const string HalloweenDungeonPrefabPath = "assets/prefabs/missions/portal/halloweenportalentry.prefab";

        #region Initialization

        private void Init()
        {
            _tierNameMap = new Dictionary<DungeonTierConfig, string>
            {
                { _configData.Tiers.Easy, "Easy" },
                { _configData.Tiers.Normal, "Normal" },
                { _configData.Tiers.Medium, "Medium" },
                { _configData.Tiers.Hard, "Hard" },
                { _configData.Tiers.Nightmare, "Nightmare" },
            };

            RemoveAllExistingDungeons();
            if (_configData.AutoSpawn.EnableAutoSpawn)
            {
                StartAutoSpawnTimer();
            }
        }

        private void RemoveAllExistingDungeons()
        {
            RemoveDungeonsOfType<XmasDungeon>();
            RemoveDungeonsOfType<HalloweenDungeon>();
        }

        private void RemoveDungeonsOfType<T>()
            where T : BaseEntity
        {
            foreach (var dungeon in UnityEngine.Object.FindObjectsOfType<T>())
            {
                //Puts($"Removing existing {typeof(T).Name} at position {dungeon.transform.position}");
                dungeon.Kill(BaseNetworkable.DestroyMode.None);
            }
        }

        #endregion

        #region Hooks

        private object CanEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            return IsDungeonCreatedEntity(entity) ? (object)true : null;
        }

        private object OnEntityTakeDamage(BaseCombatEntity victim, HitInfo info)
        {
            if (victim == null || info == null || !IsDungeonCreatedEntity(victim))
                return null;

            if (IsDungeonCreatedEntity(info.Initiator))
            {
                return false;
            }

            return null;
        }

        private bool IsDungeonCreatedEntity(BaseEntity entity)
        {
            return entity != null && _spawnedEntities.Contains(entity);
        }

        #endregion

        #region Dungeon Management

        [ChatCommand("removedun")]
        private void RemoveDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (player != null && player.IsAdmin)
            {
                RemoveExistingDungeon();
                player.ChatMessage("Dungeon removed.");
            }
        }

        [ChatCommand("dun")]
        private void AutoSpawnDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (player != null && player.IsAdmin)
            {
                AutoSpawnDungeon();
                player.ChatMessage("Attempting to spawn dungeon.");
            }
        }

        private void StartAutoSpawnTimer()
        {
            _autoSpawnTimer = timer.Every(_configData.AutoSpawn.AutoSpawnIntervalMinutes * 60f, AutoSpawnDungeon);
        }

        private void AutoSpawnDungeon()
        {
            RemoveExistingDungeon();

            foreach (var player in BasePlayer.activePlayerList)
            {
                if (TryFindDungeonSpawnPoint(player.transform.position, out var position, out var rotation))
                {
                    var selectedTier = CreateDungeon(position, rotation);
                    if (selectedTier != null)
                    {
                        NotifyPlayersOfDungeonLocation(position, selectedTier);
                        return;
                    }
                }
            }

            Puts("Failed to auto-spawn dungeon after checking all players.");
        }

        private void NotifyPlayersOfDungeonLocation(Vector3 position, DungeonTierConfig tierConfig)
        {
            if (_tierNameMap.TryGetValue(tierConfig, out string tierName))
            {
                var message = $"A {tierName} Dungeon has appeared at coordinates: ({position.x:F0}, {position.z:F0})!";
                foreach (var player in BasePlayer.activePlayerList)
                {
                    player.ShowToast(GameTip.Styles.Blue_Normal, message);
                    player.ChatMessage(message);
                }
            }
        }

        private DungeonTierConfig CreateDungeon(Vector3 position, Quaternion rotation)
        {
            string prefabPath = RandomlySelectDungeonPrefab();
            if (string.IsNullOrEmpty(prefabPath))
            {
                Puts("Failed to select a valid dungeon prefab.");
                return null;
            }

            //Puts($"Attempting to create a dungeon. Prefab: {prefabPath}");
            var dungeon = GameManager.server.CreateEntity(prefabPath, position, rotation) as BasePortal;

            if (dungeon == null)
            {
                Puts("Failed to create dungeon entity.");
                return null;
            }

            dungeon.Spawn();
            //Puts("Dungeon spawned, checking cell count...");

            var proceduralDungeon = (dungeon as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeon as HalloweenDungeon)?.dungeonInstance.Get(true);

            if (proceduralDungeon == null || proceduralDungeon.spawnedCells.Count < 1)
            {
                Puts("Insufficient cells in dungeon or failed to retrieve instance, destroying dungeon.");
                RemoveDungeonEntities();
                dungeon.Kill(BaseNetworkable.DestroyMode.None);
                return null;
            }

            //Puts($"Current dungeon cell count: {proceduralDungeon.spawnedCells.Count}");
            var selectedTier = DetermineDungeonTier(proceduralDungeon.spawnedCells.Count);

            NextTick(() =>
            {
                IntegrateEntitiesIntoDungeon(dungeon, selectedTier);
            });

            _currentDungeon = dungeon;
            return selectedTier;
        }

        private object OnPortalUse(BasePlayer player, BasePortal portal)
        {
            if (!_currentDungeon.Equals(portal))
            {
                Puts($"Portal not recognized as a plugin-created dungeon.");
                return null;
            }

            var proceduralDungeon = (portal as XmasDungeon)?.dungeonInstance.Get(true) ?? (portal as HalloweenDungeon)?.dungeonInstance.Get(true);
            if (proceduralDungeon == null)
            {
                Puts($"No valid dungeon instance associated with the portal.");
                return null;
            }

            foreach (var cell in proceduralDungeon.spawnedCells)
            {
                foreach (var group in cell.spawnGroups)
                {
                    //Puts($"Processing spawn group: {group}, Instance count: {group.spawnInstances.Count}");
                    for (int num = group.spawnInstances.Count - 1; num >= 0; num--)
                    {
                        SpawnPointInstance spawnPointInstance = group.spawnInstances[num];
                        BaseEntity entity = spawnPointInstance?.gameObject?.ToBaseEntity();

                        //Puts($"Entity found: {entity.GetType().Name}, PrefabName: {entity.PrefabName}");

                        if (entity != null && !IsDungeonCreatedEntity(entity) && (entity is NPCDwelling || entity is ScarecrowNPC || entity is LootContainer))
                        {
                            //Puts($"Removed NPC: {entity} - {entity.ShortPrefabName} at {entity.transform.position}");
                            entity.Kill();
                        }
                    }
                }
            }
            return null;
        }

        private DungeonTierConfig DetermineDungeonTier(int cellCount)
        {
            return cellCount switch
            {
                < 5 => _configData.Tiers.Easy,
                < 8 => _configData.Tiers.Normal,
                < 11 => _configData.Tiers.Medium,
                < 14 => _configData.Tiers.Hard,
                _ => _configData.Tiers.Nightmare,
            };
        }

        private void RemoveDungeonEntities()
        {
            //Puts("Removing spawned dungeon entities...");
            foreach (var entity in _spawnedEntities.Where(entity => entity != null && !entity.IsDestroyed))
            {
                entity.Kill(BaseNetworkable.DestroyMode.None);
                //Puts($"Entity {entity.ShortPrefabName} at {entity.transform.position} destroyed.");
            }

            _spawnedEntities.Clear();
            //Puts("All dungeon entities removed.");
        }

        private string RandomlySelectDungeonPrefab()
        {
            var availablePrefabs = new List<string>();

            if (_configData.DungeonSpawn.EnableXmasDungeon)
                availablePrefabs.Add(XmasDungeonPrefabPath);

            if (_configData.DungeonSpawn.EnableHalloweenDungeon)
                availablePrefabs.Add(HalloweenDungeonPrefabPath);

            return availablePrefabs.Count == 0 ? null : availablePrefabs[Random.Range(0, availablePrefabs.Count)];
        }

        private void IntegrateEntitiesIntoDungeon(BasePortal dungeon, DungeonTierConfig tierConfig)
        {
            var proceduralDungeon = (dungeon as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeon as HalloweenDungeon)?.dungeonInstance.Get(true);

            if (proceduralDungeon == null)
            {
                Puts("Failed to get procedural dungeon instance.");
                return;
            }

            var allSpawnEntries = GatherAllSpawnEntries(tierConfig);
            int remainingEntities = allSpawnEntries.Count;

            foreach (var cell in proceduralDungeon.spawnedCells)
            {
                if (remainingEntities <= 0)
                    break;

                int entityCountForCell = Mathf.Min(remainingEntities, tierConfig.AutoTurretConfig.Total);

                SetupSpawnGroup(cell, allSpawnEntries.Take(entityCountForCell).ToList(), tierConfig, entityCountForCell);
                allSpawnEntries.RemoveRange(0, entityCountForCell);
                remainingEntities -= entityCountForCell;
            }
        }

        private List<SpawnGroup.SpawnEntry> GatherAllSpawnEntries(DungeonTierConfig tierConfig)
        {
            var allSpawnEntries = new List<SpawnGroup.SpawnEntry>();

            foreach (var config in tierConfig.NpcSpawnConfigs)
            {
                if (GameManifest.pathToGuid.TryGetValue(config.PrefabName, out var guid))
                {
                    for (int i = 0; i < config.Total; i++)
                    {
                        allSpawnEntries.Add(
                            new SpawnGroup.SpawnEntry
                            {
                                prefab = new GameObjectRef { guid = guid },
                                weight = 1,
                                mobile = true,
                            }
                        );
                    }
                }
            }

            for (int i = 0; i < tierConfig.AutoTurretConfig.Total; i++)
            {
                if (GameManifest.pathToGuid.TryGetValue("assets/prefabs/npc/autoturret/autoturret_deployed.prefab", out var turretGuid))
                {
                    allSpawnEntries.Add(
                        new SpawnGroup.SpawnEntry
                        {
                            prefab = new GameObjectRef { guid = turretGuid },
                            weight = 1,
                            mobile = false,
                        }
                    );
                }
            }

            for (int i = 0; i < tierConfig.TotalLootBoxes; i++)
            {
                if (GameManifest.pathToGuid.TryGetValue("assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab", out var boxGuid))
                {
                    allSpawnEntries.Add(
                        new SpawnGroup.SpawnEntry
                        {
                            prefab = new GameObjectRef { guid = boxGuid },
                            weight = 1,
                            mobile = false,
                        }
                    );
                }
            }

            return allSpawnEntries.OrderBy(x => Random.value).ToList();
        }

        private void SetupSpawnGroup(ProceduralDungeonCell cell, List<SpawnGroup.SpawnEntry> entries, DungeonTierConfig tierConfig, int population)
        {
            var spawnGroup = cell.gameObject.AddComponent<SpawnGroup>();
            spawnGroup.prefabs = entries;
            spawnGroup.maxPopulation = population;
            spawnGroup.numToSpawnPerTickMin = 0;
            spawnGroup.numToSpawnPerTickMax = 0;
            spawnGroup.fillOnSpawn = true;
            spawnGroup.wantsInitialSpawn = true;
            spawnGroup.SpawnInitial();

            foreach (var instance in spawnGroup.spawnInstances)
            {
                var entity = instance.GetComponent<BaseEntity>();
                if (entity != null && !_spawnedEntities.Contains(entity))
                {
                    _spawnedEntities.Add(entity);
                    Puts($"{entity.ShortPrefabName} spawned at {entity.transform.position}");

                    if (entity is AutoTurret autoTurret)
                    {
                        ConfigureAutoTurret(autoTurret, tierConfig.AutoTurretConfig);
                        autoTurret.gameObject.AddComponent<TurretBehaviour>();
                    }

                    if (entity is StorageContainer box)
                    {
                        ConfigureLootBox(box);
                    }
                }
            }
        }

        private void ConfigureLootBox(StorageContainer box)
        {
            box.skinID = _configData.LootBoxConfig.SmallWoodBoxSkinID;
            AddLockToBox(box);
            FillLootBox(box.inventory, _configData.LootBoxConfig.LootItems);
        }

        private void AddLockToBox(StorageContainer box)
        {
            var codeLock = GameManager.server.CreateEntity("assets/prefabs/locks/keypad/lock.code.prefab") as CodeLock;
            if (codeLock != null)
            {
                codeLock.SetParent(box, box.GetSlotAnchorName(BaseEntity.Slot.Lock));
                codeLock.Spawn();
                codeLock.code = Random.Range(1000, 9999).ToString();
                codeLock.hasCode = true;
                codeLock.guestCode = string.Empty;
                codeLock.hasGuestCode = false;
                codeLock.guestPlayers.Clear();
                codeLock.whitelistPlayers.Clear();
                codeLock.SetFlag(BaseEntity.Flags.Locked, true);
            }
        }

        private void FillLootBox(ItemContainer container, List<ItemConfig> lootItems)
        {
            var shuffledItems = lootItems.OrderBy(_ => Random.value).ToList();
            int maxDifferentItems = _configData.LootBoxConfig.MaxDifferentItemsPerBox;
            int differentItemsCount = 0;

            foreach (var itemInfo in shuffledItems)
            {
                if (differentItemsCount >= maxDifferentItems)
                    break;

                if (Random.Range(0f, 100f) <= itemInfo.InclusionChancePercentage)
                {
                    var itemDefinition = ItemManager.FindItemDefinition(itemInfo.ShortName);
                    if (itemDefinition != null)
                    {
                        int amount = Random.Range(itemInfo.MinimumAmount, itemInfo.MaximumAmount + 1);

                        if (container.itemList.Count < container.capacity)
                        {
                            var item = ItemManager.Create(itemDefinition, amount);
                            item.MoveToContainer(container);
                            differentItemsCount++;
                        }
                    }
                }
            }
        }

        private void ConfigureAutoTurret(AutoTurret autoTurret, TurretConfig turretConfig)
        {
            autoTurret.health = turretConfig.Health;
            autoTurret.SetMaxHealth(autoTurret.health);

            var weapon = ItemManager.CreateByName(turretConfig.WeaponShortName);
            if (weapon != null)
            {
                weapon.MoveToContainer(autoTurret.inventory, 0);

                var laserSight = ItemManager.CreateByName("weapon.mod.lasersight");
                laserSight?.MoveToContainer(weapon.contents);
                autoTurret.UpdateAttachedWeapon();
            }
        }

        #endregion

        #region Utility and Cleanup

        private bool TryFindDungeonSpawnPoint(Vector3 center, out Vector3 suitablePosition, out Quaternion suitableRotation)
        {
            for (int attempt = 0; attempt < _configData.AutoSpawn.MaxSpawnAttempts; attempt++)
            {
                Vector3 candidatePosition = TerrainUtilities.GetRandomPosition(center, _configData.AutoSpawn.MinimumSearchRadius, _configData.AutoSpawn.MaximumSearchRadius);

                if (IsValidSpawnPosition(candidatePosition, out suitablePosition, out suitableRotation))
                    return true;
            }

            suitablePosition = Vector3.zero;
            suitableRotation = Quaternion.identity;
            return false;
        }

        private bool IsValidSpawnPosition(Vector3 position, out Vector3 suitablePosition, out Quaternion suitableRotation)
        {
            suitablePosition = Vector3.zero;
            suitableRotation = Quaternion.identity;

            if (TerrainUtilities.InsideRock(position, _configData.AutoSpawn.RocksAvoidanceRadius))
            {
                return false;
            }

            if (TerrainUtilities.InRadTown(position) || TerrainUtilities.HasEntityNearby(position, _configData.AutoSpawn.NearbyEntitiesAvoidanceRadius, Layers.Mask.Construction))
            {
                return false;
            }

            if (TerrainUtilities.InWater(position) || TerrainUtilities.OnRoadOrRail(position))
            {
                return false;
            }

            if (TerrainUtilities.InNoBuildZone(position, _configData.AutoSpawn.DistanceFromNoBuildZones))
            {
                return false;
            }

            if (TerrainUtilities.GetTerrainInfo(position, out var hitInfo))
            {
                suitablePosition = hitInfo.point;
                suitableRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
                return true;
            }

            return false;
        }

        private void RemoveExistingDungeon()
        {
            if (_currentDungeon != null)
            {
                //Puts("Removing existing dungeon...");
                RemoveDungeonEntities();

                if (!_currentDungeon.IsDestroyed)
                {
                    _currentDungeon.Kill(BaseNetworkable.DestroyMode.None);
                    //Puts("Current dungeon entity destroyed.");
                }

                _currentDungeon = null;
            }
            else
            {
                //Puts("No existing dungeon to remove.");
            }
        }

        private void Unload()
        {
            RemoveDungeonEntities();
            RemoveAllExistingDungeons();
            _autoSpawnTimer?.Destroy();
        }

        #endregion

        #region Terrain Utilities

        public static class TerrainUtilities
        {
            private const int WorldLayer = Layers.Mask.World;
            private const int TerrainLayer = Layers.Mask.Terrain;
            private const int PreventBuildingLayer = Layers.Mask.Prevent_Building;
            private const int ConstructionLayer = Layers.Mask.Construction;

            public static Vector3 GetRandomPosition(Vector3 center, float minRadius, float maxRadius, bool adjustToWaterHeight = false)
            {
                Vector3 randomDirection = Random.onUnitSphere;
                float randomDistance = Random.Range(minRadius, maxRadius);
                Vector3 randomPosition = center + randomDirection * randomDistance;

                randomPosition.y = adjustToWaterHeight ? TerrainMeta.WaterMap.GetHeight(randomPosition) : TerrainMeta.HeightMap.GetHeight(randomPosition);

                return randomPosition;
            }

            public static bool InsideRock(Vector3 position, float radius)
            {
                List<Collider> colliders = Pool.GetList<Collider>();
                Vis.Colliders(position, radius, colliders, WorldLayer, QueryTriggerInteraction.Ignore);

                bool isInsideRock = colliders.Any(collider =>
                    collider.name.Contains("rock", StringComparison.OrdinalIgnoreCase)
                    || collider.name.Contains("cliff", StringComparison.OrdinalIgnoreCase)
                    || collider.name.Contains("formation", StringComparison.OrdinalIgnoreCase)
                );

                Pool.FreeList(ref colliders);
                return isInsideRock;
            }

            public static bool InRadTown(Vector3 position)
            {
                return TerrainMeta.Path.Monuments.Any(monument => monument.IsInBounds(position) && monument.shouldDisplayOnMap)
                    || (TerrainMeta.TopologyMap.GetTopology(position) & (int)TerrainTopology.Enum.Monument) != 0;
            }

            public static bool HasEntityNearby(Vector3 position, float radius, int mask, string prefabName = null)
            {
                List<Collider> hitColliders = Pool.GetList<Collider>();
                GamePhysics.OverlapSphere(position, radius, hitColliders, mask, QueryTriggerInteraction.Ignore);

                bool result = hitColliders.Any(collider =>
                {
                    BaseEntity entity = collider.gameObject.ToBaseEntity();
                    return entity != null && (prefabName == null || entity.PrefabName == prefabName);
                });

                Pool.FreeList(ref hitColliders);
                return result;
            }

            public static bool InWater(Vector3 position)
            {
                return WaterLevel.Test(position, false, false);
            }

            public static bool OnRoadOrRail(Vector3 position)
            {
                int topology = TerrainMeta.TopologyMap.GetTopology(position);
                return (topology & (int)(TerrainTopology.Enum.Road | TerrainTopology.Enum.Roadside | TerrainTopology.Enum.Rail | TerrainTopology.Enum.Railside)) != 0;
            }

            public static bool GetTerrainInfo(Vector3 startPosition, out RaycastHit hitInfo, float range = 1f, LayerMask mask = default)
            {
                mask = mask == default ? TerrainLayer : mask;
                return Physics.Linecast(startPosition + Vector3.up * range, startPosition - Vector3.up * range, out hitInfo, mask);
            }

            public static bool InNoBuildZone(Vector3 position, float radius)
            {
                return Physics.CheckSphere(position, radius, PreventBuildingLayer, QueryTriggerInteraction.Ignore);
            }
        }
        #endregion

        #region Configuration

        private class ConfigData
        {
            [JsonProperty(PropertyName = "Dungeon Spawn Settings")]
            public DungeonSpawnSettings DungeonSpawn { get; set; } = new DungeonSpawnSettings();

            [JsonProperty(PropertyName = "Auto Spawn Settings")]
            public AutoSpawnSettings AutoSpawn { get; set; } = new AutoSpawnSettings();

            [JsonProperty(PropertyName = "Loot Box Config")]
            public LootBoxConfig LootBoxConfig { get; set; } = new LootBoxConfig();

            [JsonProperty(PropertyName = "Tiers")]
            public DungeonTiers Tiers { get; set; } = new DungeonTiers();

            [JsonProperty(PropertyName = "Version")]
            public VersionNumber Version { get; set; }
        }

        private class DungeonTiers
        {
            [JsonProperty(PropertyName = "Easy")]
            public DungeonTierConfig Easy { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    TotalLootBoxes = 2,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 1 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 3,
                        Health = 300,
                        WeaponShortName = "pistol.revolver",
                    },
                };

            [JsonProperty(PropertyName = "Normal")]
            public DungeonTierConfig Normal { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    TotalLootBoxes = 3,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_shotgun.prefab", Total = 3 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 3,
                        Health = 600,
                        WeaponShortName = "smg.2",
                    },
                };

            [JsonProperty(PropertyName = "Medium")]
            public DungeonTierConfig Medium { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    TotalLootBoxes = 4,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/banditguard/npc_bandit_guard.prefab", Total = 4 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 3,
                        Health = 1000,
                        WeaponShortName = "smg.mp5",
                    },
                };

            [JsonProperty(PropertyName = "Hard")]
            public DungeonTierConfig Hard { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    TotalLootBoxes = 5,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_cargo_turret_lr300.prefab", Total = 6 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 3,
                        Health = 1500,
                        WeaponShortName = "rifle.ak",
                    },
                };

            [JsonProperty(PropertyName = "Nightmare")]
            public DungeonTierConfig Nightmare { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    TotalLootBoxes = 6,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_ch47_gunner.prefab", Total = 8 },
                    },
                    AutoTurretConfig = new TurretConfig
                    {
                        Total = 3,
                        Health = 2000,
                        WeaponShortName = "rifle.ak",
                    },
                };
        }

        private class DungeonTierConfig
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { get; set; }

            [JsonProperty(PropertyName = "Total Loot Boxes")]
            public int TotalLootBoxes { get; set; }

            [JsonProperty(PropertyName = "NPC Spawn Configs", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<NpcSpawnConfig> NpcSpawnConfigs { get; set; }

            [JsonProperty(PropertyName = "Auto Turret Config")]
            public TurretConfig AutoTurretConfig { get; set; } = new TurretConfig();
        }

        public class LootBoxConfig
        {
            [JsonProperty(PropertyName = "Small Wood Box Skin ID")]
            public ulong SmallWoodBoxSkinID { get; set; } = 2998755525;

            [JsonProperty(PropertyName = "Max Different Items Per Box")]
            public int MaxDifferentItemsPerBox { get; set; } = 6;

            [JsonProperty(PropertyName = "Loot Items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ItemConfig> LootItems { get; set; } =
                new List<ItemConfig>
                {
                    new ItemConfig
                    {
                        ShortName = "ammo.rifle",
                        InclusionChancePercentage = 15,
                        MinimumAmount = 100,
                        MaximumAmount = 300,
                    },
                };
        }

        private class AutoSpawnSettings
        {
            [JsonProperty(PropertyName = "Enable Auto Spawn")]
            public bool EnableAutoSpawn { get; set; } = false;

            [JsonProperty(PropertyName = "Auto Spawn Interval Minutes")]
            public float AutoSpawnIntervalMinutes { get; set; } = 30f;

            [JsonProperty(PropertyName = "Minimum Search Radius For Dungeon Position")]
            public float MinimumSearchRadius { get; set; } = 20f;

            [JsonProperty(PropertyName = "Maximum Search Radius For Dungeon Position")]
            public float MaximumSearchRadius { get; set; } = 50f;

            [JsonProperty(PropertyName = "Max Spawn Attempts")]
            public int MaxSpawnAttempts { get; set; } = 100;

            [JsonProperty(PropertyName = "Nearby Entities Avoidance Radius")]
            public float NearbyEntitiesAvoidanceRadius { get; set; } = 10f;

            [JsonProperty(PropertyName = "Rocks Avoidance Radius")]
            public float RocksAvoidanceRadius { get; set; } = 10f;

            [JsonProperty(PropertyName = "Distance From No Build Zones")]
            public float DistanceFromNoBuildZones { get; set; } = 10f;
        }

        private class NpcSpawnConfig
        {
            [JsonProperty(PropertyName = "Prefab")]
            public string PrefabName { get; set; }

            [JsonProperty(PropertyName = "Total")]
            public int Total { get; set; }
        }

        private class TurretConfig
        {
            [JsonProperty(PropertyName = "Total")]
            public int Total { get; set; } = 3;

            [JsonProperty(PropertyName = "Health")]
            public float Health { get; set; } = 1000f;

            [JsonProperty(PropertyName = "Weapon Short Name")]
            public string WeaponShortName { get; set; } = "rifle.ak";
        }

        private class DungeonSpawnSettings
        {
            [JsonProperty(PropertyName = "Enable Xmas Dungeon")]
            public bool EnableXmasDungeon { get; set; } = true;

            [JsonProperty(PropertyName = "Enable Halloween Dungeon")]
            public bool EnableHalloweenDungeon { get; set; } = false;
        }

        public class ItemConfig
        {
            [JsonProperty(PropertyName = "ShortName")]
            public string ShortName { get; set; }

            [JsonProperty(PropertyName = "Inclusion Chance Percentage")]
            public float InclusionChancePercentage { get; set; }

            [JsonProperty(PropertyName = "Minimum Amount")]
            public int MinimumAmount { get; set; }

            [JsonProperty(PropertyName = "Maximum Amount")]
            public int MaximumAmount { get; set; }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _configData = Config.ReadObject<ConfigData>();
                if (_configData == null)
                {
                    LoadDefaultConfig();
                }
            }
            catch (Exception ex)
            {
                PrintError($"The configuration file is corrupted: {ex}");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            PrintWarning("Creating a new configuration file");
            _configData = new ConfigData();
            _configData.Version = Version;
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(_configData, true);
        }

        #endregion

        #region Turret Behaviour Class

        public class TurretBehaviour : MonoBehaviour
        {
            private AutoTurret _turret;
            private const float SearchDistance = 15f;
            private const int MaxReserveAmmo = 1000;

            private void Awake()
            {
                InitializeTurret();
            }

            private void InitializeTurret()
            {
                _turret = GetComponent<AutoTurret>();
                var triggerCollider = _turret.targetTrigger.GetComponent<SphereCollider>();
                triggerCollider.enabled = false;

                _turret.SetPeacekeepermode(false);
                _turret.InitiateStartup();
                _turret.SetIsOnline(true);
                _turret.CancelInvoke(_turret.ServerTick);
                _turret.SetTarget(null);
                _turret.InvokeRepeating(PerformTurretCycle, 1f, 0.01f);
                _turret.InvokeRepeating(ScanForTargets, 2f, 1f);
                _turret.isLootable = false;
                _turret.dropFloats = false;
                _turret.dropsLoot = false;

                _turret.SendNetworkUpdateImmediate();
                InvokeRepeating(nameof(RefillAmmo), 2f, 30f);
            }

            private void RefillAmmo()
            {
                if (_turret.AttachedWeapon is not BaseProjectile baseProjectile || baseProjectile.primaryMagazine?.ammoType == null)
                    return;

                var ammoType = baseProjectile.primaryMagazine.ammoType;
                int currentAmmoCount = CalculateCurrentAmmoCount(ammoType);

                if (currentAmmoCount < MaxReserveAmmo)
                {
                    int ammoNeeded = MaxReserveAmmo - currentAmmoCount;
                    Item ammoItem = ItemManager.Create(ammoType, ammoNeeded);
                    ammoItem?.MoveToContainer(_turret.inventory);

                    _turret.UpdateTotalAmmo();
                    _turret.EnsureReloaded();
                    _turret.SendNetworkUpdateImmediate();
                }
            }

            private int CalculateCurrentAmmoCount(ItemDefinition ammoType)
            {
                return _turret.inventory.itemList.Where(item => item.info == ammoType).Sum(item => item.amount);
            }

            private void ScanForTargets()
            {
                var entityContents = _turret.targetTrigger.entityContents ??= new HashSet<BaseEntity>();
                entityContents.Clear();

                int foundTargets = BaseEntity.Query.Server.GetPlayersInSphereFast(transform.position, SearchDistance, AIBrainSenses.playerQueryResults, IsTargetValid);

                if (foundTargets == 0)
                    return;

                _turret.authDirty = true;

                for (int i = 0; i < foundTargets; i++)
                {
                    var player = AIBrainSenses.playerQueryResults[i];
                    if (Interface.CallHook("OnEntityEnter", _turret.targetTrigger, player) != null || player.IsSleeping() || (player.InSafeZone() && !player.IsHostile()))
                        continue;

                    entityContents.Add(player);
                }
            }

            private bool IsTargetValid(BasePlayer player) => player != null && !player.IsNpc;

            private void PerformTurretCycle()
            {
                if (_turret.isClient || _turret.IsDestroyed)
                    return;

                float deltaTime = (float)_turret.timeSinceLastServerTick;
                _turret.timeSinceLastServerTick = 0f;

                if (_turret.IsOnline() && !_turret.IsBeingControlled)
                {
                    if (!_turret.HasTarget())
                    {
                        _turret.IdleTick(deltaTime);
                    }
                    else
                    {
                        ExecuteTargetEngagement();
                    }
                }

                _turret.UpdateFacingToTarget(deltaTime);
                UpdateAmmoStatus();
            }

            private void ExecuteTargetEngagement()
            {
                if (Time.realtimeSinceStartup >= _turret.nextVisCheck)
                {
                    _turret.nextVisCheck = Time.realtimeSinceStartup + UnityEngine.Random.Range(0.2f, 0.3f);
                    _turret.targetVisible = _turret.ObjectVisible(_turret.target);

                    if (_turret.targetVisible)
                        _turret.lastTargetSeenTime = Time.realtimeSinceStartup;
                }

                _turret.EnsureReloaded();
                if (ShouldFireAtTarget())
                {
                    var weapon = _turret.GetAttachedWeapon();
                    FireWeapon(weapon);
                }

                ValidateTargetEngagement();
            }

            private bool ShouldFireAtTarget()
            {
                return Time.time >= _turret.nextShotTime
                    && _turret.targetVisible
                    && Mathf.Abs(_turret.AngleToTarget(_turret.target, _turret.currentAmmoGravity != 0f)) < _turret.GetMaxAngleForEngagement();
            }

            private void FireWeapon(BaseProjectile weapon)
            {
                if (weapon == null)
                {
                    _turret.nextShotTime = Time.time + 1f;
                    return;
                }

                if (weapon.primaryMagazine.contents > 0)
                {
                    _turret.FireAttachedGun(_turret.AimOffset(_turret.target), _turret.aimCone, null, _turret.PeacekeeperMode() ? _turret.target : null);
                    float delay = weapon.isSemiAuto ? weapon.repeatDelay * 1.5f : weapon.repeatDelay;
                    delay = weapon.ScaleRepeatDelay(delay);
                    _turret.nextShotTime = Time.time + delay;
                }
                else
                {
                    _turret.nextShotTime = Time.time + 5f;
                }
            }

            private void ValidateTargetEngagement()
            {
                var targetPlayer = _turret.target as BasePlayer;
                if (_turret.target != null && (!IsValidSteamPlayer(targetPlayer) || _turret.target.IsDead() || TimeExceedsLastSeen() || DistanceExceedsSightRange() || ShouldTargetBeReset()))
                {
                    _turret.SetTarget(null);
                }
            }

            private static bool IsValidSteamPlayer(BasePlayer targetPlayer) => targetPlayer != null && targetPlayer.userID.IsSteamId();

            private bool TimeExceedsLastSeen() => Time.realtimeSinceStartup - _turret.lastTargetSeenTime > 3f;

            private bool DistanceExceedsSightRange() => Vector3.Distance(_turret.transform.position, _turret.target.transform.position) > _turret.sightRange;

            private bool ShouldTargetBeReset() => _turret.PeacekeeperMode() && !_turret.IsEntityHostile(_turret.target);

            private void UpdateAmmoStatus()
            {
                if (_turret.totalAmmoDirty && Time.time > _turret.nextAmmoCheckTime)
                {
                    _turret.UpdateTotalAmmo();
                    _turret.totalAmmoDirty = false;
                    _turret.nextAmmoCheckTime = Time.time + 0.5f;
                }
            }

            private void OnDestroy()
            {
                CancelInvoke(nameof(RefillAmmo));
            }
        }

        #endregion
    }
}