using System;
using System.Collections.Generic;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
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 static Dungeons _instance;
        private ConfigData _configData;
        private DynamicConfigFile _dataFile;
        private DungeonData _dungeonData;
        private Timer _autoSpawnTimer;
        private Timer _dungeonCheckTimer;
        private Timer _updateMarkersTimer;
        private Dictionary<DungeonTierConfig, string> _tierNameMap;
        private const ulong OwnerID = 1020304050;

        #region Chat Commands

        [ChatCommand("dun")]
        private void CreateDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (!IsAdmin(player))
                return;

            var result = AutoSpawnDungeon();
            NotifyPlayer(player, result);
        }

        [ChatCommand("removeinactivedungeons")]
        private void RemoveInactiveDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (!IsAdmin(player))
                return;

            var removedDungeons = RemoveInactiveDungeons();
            NotifyPlayerOnRemoval(player, removedDungeons, "NoInactiveDungeonsToRemove");
        }

        [ChatCommand("removedungeons")]
        private void RemoveDungeonCommand(BasePlayer player, string command, string[] args)
        {
            if (!IsAdmin(player))
                return;

            var removedDungeons = RemoveAllDungeons();
            NotifyPlayerOnRemoval(player, removedDungeons, "NoDungeonsToRemove");
        }

        #endregion

        #region Initialization

        private void OnServerInitialized()
        {
            _instance = this;
            CleanupExistingEntities();
            InitializeTierNameMap();
            LoadData();

            if (_configData.DungeonSpawn.EnableAutoSpawn)
            {
                StartAutoSpawnTimer();
            }

            StartDungeonCheckTimer();
            StartUpdateMarkersTimer();
        }

        private void CleanupExistingEntities()
        {
            foreach (
                var entityType in new Type[]
                {
                    typeof(VendingMachineMapMarker),
                    typeof(MapMarkerGenericRadius),
                    typeof(BasePortal),
                    typeof(ProceduralDynamicDungeon),
                    typeof(AutoTurret),
                    typeof(NPCPlayer),
                    typeof(StorageContainer),
                }
            )
            {
                foreach (var obj in UnityEngine.Object.FindObjectsOfType(entityType))
                {
                    if (obj is BaseEntity baseEntity && baseEntity.OwnerID == OwnerID)
                    {
                        baseEntity.Kill(BaseNetworkable.DestroyMode.None);
                    }
                }
            }
        }

        private void InitializeTierNameMap()
        {
            _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" },
            };
        }

        private void LoadData()
        {
            _dataFile = Interface.Oxide.DataFileSystem.GetFile("Dungeons_Data");
            _dungeonData = _dataFile.ReadObject<DungeonData>() ?? new DungeonData();
        }

        private void SaveData()
        {
            _dataFile.WriteObject(_dungeonData);
        }

        #endregion

        #region Hooks

        private object CanEntityTakeDamage(BaseCombatEntity victim, HitInfo hitInfo)
        {
            if (victim == null || hitInfo == null)
                return null;

            if (hitInfo.Initiator == null)
                return null;

            if (IsValidSteamPlayer(hitInfo.Initiator as BasePlayer) && IsDungeonEntity(victim))
                return true;

            if (IsDungeonEntity(hitInfo.Initiator) && IsDungeonEntity(victim))
                return true;

            return null;
        }

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

            if (hitInfo.Initiator == null)
                return null;

            if (IsValidSteamPlayer(hitInfo.Initiator as BasePlayer) && IsDungeonEntity(victim))
                return null;

            if (IsDungeonEntity(hitInfo.Initiator) && IsDungeonEntity(victim))
                return true;

            return null;
        }

        private void OnServerSave() => SaveData();

        #endregion

        #region Dungeon Management

        private void StartUpdateMarkersTimer()
        {
            _updateMarkersTimer = timer.Every(30f, UpdateMarkers);
        }

        private void UpdateMarkers()
        {
            var mapMarkers = UnityEngine.Object.FindObjectsOfType<MapMarkerGenericRadius>();
            foreach (var marker in mapMarkers.Where(marker => marker.IsValid()))
            {
                marker.SendUpdate();
            }
        }

        private void StartAutoSpawnTimer()
        {
            _autoSpawnTimer = timer.Every(
                _configData.DungeonSpawn.AutoSpawnCycleInterval,
                () =>
                {
                    var result = AutoSpawnDungeon();
                    LogDungeonSpawnResult(result);
                }
            );
        }

        private void StartDungeonCheckTimer()
        {
            _dungeonCheckTimer = timer.Every(
                60f,
                () =>
                {
                    var removedDungeons = RemoveInactiveDungeons();
                    if (removedDungeons.Count > 0)
                    {
                        foreach (var dungeon in removedDungeons)
                        {
                            LogDungeonRemoval(dungeon);
                        }
                    }
                }
            );
        }

        private enum DungeonSpawnResult
        {
            Success,
            MaxActiveDungeonsReached,
            NoValidSpawnLocation,
        }

        private DungeonSpawnResult AutoSpawnDungeon()
        {
            if (_dungeonData.ActiveDungeons.Count >= _configData.DungeonSpawn.MaxTotalActiveDungeons)
            {
                return DungeonSpawnResult.MaxActiveDungeonsReached;
            }

            if (TryFindDungeonSpawnPoint(out var position, out var rotation))
            {
                var selectedTier = CreateDungeon(position, rotation);
                if (selectedTier != null)
                {
                    var tierName = _tierNameMap[selectedTier];
                    CreateDungeonMarkers(position, tierName);
                    NotifyPlayersOfNewDungeon(position, tierName);
                    return DungeonSpawnResult.Success;
                }
            }
            return DungeonSpawnResult.NoValidSpawnLocation;
        }

        private DungeonTierConfig CreateDungeon(Vector3 position, Quaternion rotation)
        {
            string prefabPath = GetRandomDungeonPrefab();
            if (string.IsNullOrEmpty(prefabPath))
                return null;

            var dungeonPortal = GameManager.server.CreateEntity(prefabPath, position, rotation) as BasePortal;
            if (dungeonPortal == null)
                return null;

            dungeonPortal.OwnerID = OwnerID;
            dungeonPortal.Spawn();

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

            if (!(dungeonPortal is XmasDungeon xmasDungeon || dungeonPortal is HalloweenDungeon halloweenDungeon))
            {
                dungeonPortal.Kill(BaseNetworkable.DestroyMode.None);
                return null;
            }
            if (proceduralDungeon == null || proceduralDungeon.spawnedCells.Count < 1)
            {
                dungeonPortal.Kill(BaseNetworkable.DestroyMode.None);
                return null;
            }

            var selectedTier = GetDungeonTier(proceduralDungeon.spawnedCells.Count);

            var activeDungeon = new ActiveDungeon
            {
                PortalId = dungeonPortal.net.ID.Value,
                DynamicDungeonId = proceduralDungeon.net.ID.Value,
                Position = position,
                TierName = _tierNameMap[selectedTier],
                EntityIds = new List<ulong>(),
            };

            NextTick(() => PopulateDungeon(dungeonPortal, selectedTier, activeDungeon));
            _dungeonData.ActiveDungeons.Add(activeDungeon);

            return selectedTier;
        }

        private void PopulateDungeon(BasePortal dungeon, DungeonTierConfig tierConfig, ActiveDungeon activeDungeon)
        {
            var proceduralDungeon = (dungeon as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeon as HalloweenDungeon)?.dungeonInstance.Get(true);
            if (proceduralDungeon == null)
                return;

            var allSpawnEntries = GenerateSpawnEntries(tierConfig);
            DistributeEntitiesAcrossCells(proceduralDungeon, allSpawnEntries, tierConfig, activeDungeon);
        }

        private object OnPortalUse(BasePlayer player, BasePortal portal)
        {
            if (_dungeonData.ActiveDungeons.All(d => d.PortalId != portal.net.ID.Value))
            {
                return null;
            }

            var proceduralDungeon = (portal as XmasDungeon)?.dungeonInstance.Get(true) ?? (portal as HalloweenDungeon)?.dungeonInstance.Get(true);
            if (proceduralDungeon == null)
            {
                return null;
            }

            foreach (var cell in proceduralDungeon.spawnedCells)
            {
                foreach (var group in cell.spawnGroups)
                {
                    for (int num = group.spawnInstances.Count - 1; num >= 0; num--)
                    {
                        SpawnPointInstance spawnPointInstance = group.spawnInstances[num];
                        BaseEntity entity = spawnPointInstance?.gameObject?.ToBaseEntity();

                        if (entity != null && !IsDungeonEntity(entity) && (entity is NPCDwelling || entity is ScarecrowNPC || entity is LootContainer))
                        {
                            entity.Kill();
                        }
                    }
                }
            }
            return null;
        }

        private List<SpawnGroup.SpawnEntry> GenerateSpawnEntries(DungeonTierConfig tierConfig)
        {
            var allSpawnEntries = new List<SpawnGroup.SpawnEntry>();
            AddNpcEntries(tierConfig, allSpawnEntries);
            AddTurretEntries(tierConfig, allSpawnEntries);
            AddLootEntries(tierConfig, allSpawnEntries);
            return allSpawnEntries.OrderBy(_ => Random.value).ToList();
        }

        private void AddNpcEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
        {
            foreach (var config in tierConfig.NpcSpawnConfigs)
            {
                if (GameManifest.pathToGuid.TryGetValue(config.PrefabName, out var guid))
                {
                    spawnEntries.AddRange(
                        Enumerable.Repeat(
                            new SpawnGroup.SpawnEntry
                            {
                                prefab = new GameObjectRef { guid = guid },
                                weight = 1,
                                mobile = true,
                            },
                            config.Total
                        )
                    );
                }
            }
        }

        private void AddTurretEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
        {
            var prefabName = "assets/prefabs/npc/autoturret/autoturret_deployed.prefab";
            if (GameManifest.pathToGuid.TryGetValue(prefabName, out var turretGuid))
            {
                spawnEntries.AddRange(
                    Enumerable.Repeat(
                        new SpawnGroup.SpawnEntry
                        {
                            prefab = new GameObjectRef { guid = turretGuid },
                            weight = 1,
                            mobile = false,
                        },
                        tierConfig.AutoTurretConfig.Total
                    )
                );
            }
        }

        private void AddLootEntries(DungeonTierConfig tierConfig, List<SpawnGroup.SpawnEntry> spawnEntries)
        {
            var prefabName = "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab";
            if (GameManifest.pathToGuid.TryGetValue(prefabName, out var boxGuid))
            {
                spawnEntries.AddRange(
                    Enumerable.Repeat(
                        new SpawnGroup.SpawnEntry
                        {
                            prefab = new GameObjectRef { guid = boxGuid },
                            weight = 1,
                            mobile = false,
                        },
                        tierConfig.TotalLootBoxes
                    )
                );
            }
        }

        private void DistributeEntitiesAcrossCells(ProceduralDynamicDungeon proceduralDungeon, List<SpawnGroup.SpawnEntry> allSpawnEntries, DungeonTierConfig tierConfig, ActiveDungeon activeDungeon)
        {
            var totalCells = proceduralDungeon.spawnedCells.Count;
            if (totalCells == 0)
                return;

            var distribution = DistributeEntities(allSpawnEntries.Count, totalCells);
            var entryIndex = 0;
            for (var i = 0; i < totalCells; i++)
            {
                var entitiesInCell = distribution[i];
                if (entitiesInCell > 0 && entryIndex + entitiesInCell <= allSpawnEntries.Count)
                {
                    var cellEntries = allSpawnEntries.GetRange(entryIndex, entitiesInCell);
                    SetupCellSpawnGroup(proceduralDungeon.spawnedCells[i], cellEntries, tierConfig, activeDungeon);
                    entryIndex += entitiesInCell;
                }
            }
        }

        private int[] DistributeEntities(int totalEntities, int totalCells)
        {
            var entitiesPerCell = new int[totalCells];
            var baseCount = totalEntities / totalCells;
            var extra = totalEntities % totalCells;

            for (var i = 0; i < totalCells; i++)
            {
                entitiesPerCell[i] = baseCount + (i < extra ? 1 : 0);
            }

            return entitiesPerCell;
        }

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

            foreach (var entity in spawnGroup.spawnInstances.Select(instance => instance.GetComponent<BaseEntity>()).Where(entity => entity != null))
            {
                entity.OwnerID = OwnerID;
                if (!activeDungeon.EntityIds.Contains(entity.net.ID.Value))
                {
                    activeDungeon.EntityIds.Add(entity.net.ID.Value);
                    if (entity is NPCPlayer)
                    {
                        activeDungeon.NpcIds.Add(entity.net.ID.Value);
                    }
                    if (entity is AutoTurret turret)
                    {
                        ConfigureTurret(turret, tierConfig.AutoTurretConfig);
                        turret.gameObject.AddComponent<TurretMonoBehaviour>();
                    }
                    if (entity is StorageContainer storage)
                    {
                        ConfigureStorage(storage);
                    }
                }
            }
        }

        private void ConfigureTurret(AutoTurret turret, TurretConfig turretConfig)
        {
            turret.health = turretConfig.Health;
            turret.SetMaxHealth(turret.health);

            var weapon = ItemManager.CreateByName(turretConfig.WeaponShortName);
            weapon?.MoveToContainer(turret.inventory, 0);

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

            turret.UpdateAttachedWeapon();
        }

        private void ConfigureStorage(StorageContainer storage)
        {
            storage.skinID = _configData.LootBoxConfig.SmallWoodBoxSkinID;
            AddCodeLock(storage);
            PopulateStorage(storage.inventory, _configData.LootBoxConfig.LootItems);
        }

        private void AddCodeLock(StorageContainer storage)
        {
            var prefabName = "assets/prefabs/locks/keypad/lock.code.prefab";
            var codeLock = GameManager.server.CreateEntity(prefabName) as CodeLock;
            if (codeLock == null)
                return;

            codeLock.SetParent(storage, storage.GetSlotAnchorName(BaseEntity.Slot.Lock));
            codeLock.Spawn();
            codeLock.code = Random.Range(1000, 9999).ToString();
            codeLock.hasCode = true;
            codeLock.SetFlag(BaseEntity.Flags.Locked, true);
        }

        private void PopulateStorage(ItemContainer container, List<ItemConfig> lootItems)
        {
            var shuffledItems = lootItems.OrderBy(_ => Random.value).ToList();
            var differentItemsCount = 0;

            foreach (var itemConfig in shuffledItems)
            {
                if (differentItemsCount >= _configData.LootBoxConfig.MaxDifferentItemsPerBox)
                {
                    break;
                }

                if (Random.value * 100f <= itemConfig.InclusionChancePercentage)
                {
                    var itemDefinition = ItemManager.FindItemDefinition(itemConfig.ShortName);
                    if (itemDefinition != null)
                    {
                        var amount = Random.Range(itemConfig.MinimumAmount, itemConfig.MaximumAmount + 1);
                        if (container.itemList.Count < container.capacity)
                        {
                            var item = ItemManager.Create(itemDefinition, amount);
                            item.MoveToContainer(container);
                            differentItemsCount++;
                        }
                    }
                }
            }
        }

        private List<ActiveDungeon> RemoveInactiveDungeons()
        {
            var removedDungeons = new List<ActiveDungeon>();

            foreach (var dungeon in _dungeonData.ActiveDungeons.ToList())
            {
                if (CanBeRemoved(dungeon))
                {
                    removedDungeons.Add(dungeon);
                    DestroyDungeon(dungeon);
                }
            }

            return removedDungeons;
        }

        private void DestroyDungeon(ActiveDungeon dungeon)
        {
            DestroyEntities(dungeon);
            RemoveDungeonMarkers(dungeon);

            _dungeonData.ActiveDungeons.Remove(dungeon);
            var portal = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.PortalId)) as BasePortal;
            portal?.Kill(BaseNetworkable.DestroyMode.None);
            SaveData();
        }

        private void DestroyEntities(ActiveDungeon dungeon)
        {
            foreach (var entityId in dungeon.EntityIds)
            {
                var entity = BaseNetworkable.serverEntities.Find(new NetworkableId(entityId)) as BaseEntity;
                entity?.Kill(BaseNetworkable.DestroyMode.None);
            }

            dungeon.EntityIds.Clear();
        }

        private void RemoveDungeonMarkers(ActiveDungeon dungeon)
        {
            foreach (var marker in _dungeonData.ActiveDungeonMarkers.Where(m => m.Position == dungeon.Position).ToList())
            {
                var vendingMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.VendingMarker)) as BaseEntity;
                var radiusMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.RadiusMarker)) as BaseEntity;

                vendingMarker?.Kill(BaseNetworkable.DestroyMode.None);
                radiusMarker?.Kill(BaseNetworkable.DestroyMode.None);

                _dungeonData.ActiveDungeonMarkers.Remove(marker);
            }
        }

        private IEnumerable<ActiveDungeon> RemoveAllDungeons()
        {
            var removedDungeons = new List<ActiveDungeon>(_dungeonData.ActiveDungeons);
            foreach (var dungeon in removedDungeons)
            {
                DestroyDungeon(dungeon);
            }
            return removedDungeons;
        }

        #endregion

        #region Utility Methods

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

        private void NotifyPlayersOfNewDungeon(Vector3 position, string tierName)
        {
            var gridPosition = PhoneController.PositionToGridCoord(position);
            foreach (var player in BasePlayer.activePlayerList)
            {
                var message = Msg(player.UserIDString, "DungeonAppeared", tierName, gridPosition);
                player.ChatMessage(message);
                player.ShowToast(GameTip.Styles.Blue_Normal, message);
            }
        }

        private void NotifyPlayer(BasePlayer player, DungeonSpawnResult result)
        {
            var messageKey = result switch
            {
                DungeonSpawnResult.Success => "DungeonSpawnedSuccess",
                DungeonSpawnResult.MaxActiveDungeonsReached => "MaxActiveDungeonsReached",
                DungeonSpawnResult.NoValidSpawnLocation => "DungeonSpawnFailed",
                _ => null,
            };
            player.ChatMessage(Msg(player.UserIDString, messageKey));
        }

        private void NotifyPlayerOnRemoval(BasePlayer player, IEnumerable<ActiveDungeon> removedDungeons, string noDungeonMessage)
        {
            if (!removedDungeons.Any())
            {
                player.ChatMessage(Msg(player.UserIDString, noDungeonMessage));
                return;
            }

            foreach (var dungeon in removedDungeons)
            {
                var gridPosition = PhoneController.PositionToGridCoord(dungeon.Position);
                player.ChatMessage(Msg(player.UserIDString, "DungeonRemovedDetail", dungeon.TierName, gridPosition));
            }
        }

        private void LogDungeonSpawnResult(DungeonSpawnResult result)
        {
            var logMessage = result switch
            {
                DungeonSpawnResult.Success => "Dungeon spawned successfully.",
                DungeonSpawnResult.MaxActiveDungeonsReached => "Cannot spawn: Maximum number of active dungeons reached.",
                DungeonSpawnResult.NoValidSpawnLocation => "Could not find a valid location to spawn a new dungeon.",
                _ => null,
            };
            if (logMessage != null)
                Puts(logMessage);
        }

        private void LogDungeonRemoval(ActiveDungeon dungeon)
        {
            var gridPosition = PhoneController.PositionToGridCoord(dungeon.Position);
            Puts($"Removed inactive dungeon of tier {dungeon.TierName} at grid {gridPosition}.");
        }

        #endregion

        #region Helper and Utility Methods

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

        private bool IsDungeonEntity(BaseEntity entity) => entity != null && _dungeonData.ActiveDungeons.Any(d => d.EntityIds.Contains(entity.net.ID.Value));

        private string GetRandomDungeonPrefab()
        {
            var prefabs = new List<string>();
            if (_configData.DungeonSpawn.EnableXmasDungeon)
                prefabs.Add("assets/prefabs/missions/portal/xmasportalentry.prefab");
            if (_configData.DungeonSpawn.EnableHalloweenDungeon)
                prefabs.Add("assets/prefabs/missions/portal/halloweenportalentry.prefab");
            return prefabs.Count > 0 ? prefabs[Random.Range(0, prefabs.Count)] : null;
        }

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

            if (!CollidersChecks.GetTerrainInfo(position, out var hitInfo))
                return false;
            if (CollidersChecks.InsideRock(position, 10f))
                return false;
            if (CollidersChecks.InWater(position))
                return false;
            if (CollidersChecks.OnRoadOrRail(position))
                return false;
            if (CollidersChecks.InNoBuildZone(position, 10f))
                return false;

            return SetSuitablePositionAndRotation(position, hitInfo, out suitablePosition, out suitableRotation);
        }

        private static bool SetSuitablePositionAndRotation(Vector3 position, RaycastHit hitInfo, out Vector3 suitablePosition, out Quaternion suitableRotation)
        {
            suitablePosition = hitInfo.point;
            suitableRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
            return true;
        }

        private void CreateDungeonMarkers(Vector3 position, string tierName)
        {
            var prefabVendingMarker = "assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab";
            var vendingMarker = CreateMapMarker<VendingMachineMapMarker>(prefabVendingMarker, position);
            vendingMarker.OwnerID = OwnerID;
            vendingMarker.markerShopName = $"Dungeon: {tierName}";
            vendingMarker.SendNetworkUpdate();

            var prefabRadiusMarker = "assets/prefabs/tools/map/genericradiusmarker.prefab";
            var radiusMarker = CreateMapMarker<MapMarkerGenericRadius>(prefabRadiusMarker, position);
            radiusMarker.OwnerID = OwnerID;
            radiusMarker.alpha = 0.75f;
            radiusMarker.radius = 0.5f;
            radiusMarker.color2 = GetMarkerColorByTier(tierName);
            radiusMarker.SendUpdate();
            radiusMarker.SendNetworkUpdate();

            _dungeonData.ActiveDungeonMarkers.Add(
                new DungeonMarker
                {
                    VendingMarker = vendingMarker.net.ID.Value,
                    RadiusMarker = radiusMarker.net.ID.Value,
                    Position = position,
                    Tier = tierName,
                }
            );
        }

        private T CreateMapMarker<T>(string prefab, Vector3 position)
            where T : BaseEntity
        {
            var entity = GameManager.server.CreateEntity(prefab, position) as T;
            entity?.Spawn();
            return entity;
        }

        private static Color GetMarkerColorByTier(string tierName)
        {
            return tierName switch
            {
                "Easy" => Color.green,
                "Normal" or "Medium" => Color.yellow,
                "Hard" => Color.red,
                "Nightmare" => Color.black,
                _ => Color.white,
            };
        }

        private bool CanBeRemoved(ActiveDungeon dungeon)
        {
            return AreAllNpcsGone(dungeon) && NoPlayersInside(dungeon);
        }

        private bool AreAllNpcsGone(ActiveDungeon dungeon)
        {
            return dungeon.NpcIds.All(npcId =>
            {
                var npc = BaseNetworkable.serverEntities.Find(new NetworkableId(npcId)) as BaseEntity;
                return npc == null || npc.IsDestroyed;
            });
        }

        private bool NoPlayersInside(ActiveDungeon dungeon)
        {
            var dynamicDungeon = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.DynamicDungeonId)) as ProceduralDynamicDungeon;
            return dynamicDungeon == null || !dynamicDungeon.ContainsAnyPlayers();
        }

        private bool TryFindDungeonSpawnPoint(out Vector3 position, out Quaternion rotation)
        {
            var mapSize = World.Size;
            for (var attempt = 0; attempt < 1000; attempt++)
            {
                var candidatePosition = new Vector3(Random.Range(-mapSize / 2, mapSize / 2), 0, Random.Range(-mapSize / 2, mapSize / 2));

                if (IsValid(candidatePosition, out position, out rotation) && IsSafeFromOtherDungeons(position) && !IsCloseToMonuments(position))
                {
                    return true;
                }
            }

            position = Vector3.zero;
            rotation = Quaternion.identity;
            return false;
        }

        private bool IsSafeFromOtherDungeons(Vector3 position)
        {
            return _dungeonData.ActiveDungeons.All(dungeon => Vector3.Distance(position, dungeon.Position) >= _configData.DungeonSpawn.MinDistanceBetweenDungeons);
        }

        private bool IsCloseToMonuments(Vector3 position)
        {
            return TerrainMeta.Path.Monuments.Any(mon => Vector3.Distance(position, mon.transform.position) < _configData.DungeonSpawn.MinDistanceFromMonuments);
        }

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

        #endregion

        #region Cleanup

        private void Unload()
        {
            RemoveAllDungeons();
            _autoSpawnTimer?.Destroy();
            _dungeonCheckTimer?.Destroy();
            _updateMarkersTimer?.Destroy();
        }

        #endregion

        #region Configuration

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

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

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

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

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

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

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

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

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

        private class DungeonTierConfig
        {
            [JsonProperty(PropertyName = "Maximum 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 NpcSpawnConfig
        {
            [JsonProperty(PropertyName = "Prefab")]
            public string PrefabName { get; set; }

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

        private class TurretConfig
        {
            [JsonProperty(PropertyName = "Maximum")]
            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; } = true;

            [JsonProperty(PropertyName = "Max Total Active Dungeons")]
            public int MaxTotalActiveDungeons { get; set; } = 4;

            [JsonProperty(PropertyName = "Min Distance From Monuments")]
            public float MinDistanceFromMonuments { get; set; } = 100f;

            [JsonProperty(PropertyName = "Min Distance Between Dungeons")]
            public float MinDistanceBetweenDungeons { get; set; } = 50f;

            [JsonProperty(PropertyName = "Auto Spawn Cycle Interval")]
            public float AutoSpawnCycleInterval { get; set; } = 60f;

            [JsonProperty(PropertyName = "Enable Auto Spawn")]
            public bool EnableAutoSpawn { get; set; } = true;
        }

        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>() ?? new ConfigData();
            }
            catch (Exception ex)
            {
                PrintError($"The configuration file is corrupt: {ex}");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

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

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

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(
                new Dictionary<string, string>
                {
                    ["NoInactiveDungeonsToRemove"] = "There are no inactive dungeons to remove.",
                    ["NoDungeonsToRemove"] = "There are no dungeons to remove.",
                    ["DungeonRemovedDetail"] = "Removed dungeon of tier {0} at grid {1}.",
                    ["AttemptSpawnDungeons"] = "Attempting to spawn dungeons.",
                    ["DungeonAppeared"] = "A {0} Dungeon has appeared at grid {1}!",
                    ["DungeonSpawnFailed"] = "Could not find a valid location to spawn a new dungeon.",
                    ["DungeonSpawnedSuccess"] = "Dungeon spawned successfully.",
                    ["MaxActiveDungeonsReached"] = "Cannot spawn: Maximum number of active dungeons reached.",
                },
                this
            );
        }

        private string Msg(string userId, string key, params object[] args)
        {
            return string.Format(lang.GetMessage(key, this, userId), args);
        }

        #endregion

        #region Data Classes

        private class DungeonData
        {
            public List<ActiveDungeon> ActiveDungeons { get; set; } = new List<ActiveDungeon>();
            public List<DungeonMarker> ActiveDungeonMarkers { get; set; } = new List<DungeonMarker>();
        }

        private class ActiveDungeon
        {
            public ulong PortalId { get; set; }
            public ulong DynamicDungeonId { get; set; }
            public Vector3 Position { get; set; }
            public string TierName { get; set; }
            public List<ulong> EntityIds { get; set; } = new List<ulong>();
            public List<ulong> NpcIds { get; set; } = new List<ulong>();
        }

        private class DungeonMarker
        {
            public ulong VendingMarker { get; set; }
            public ulong RadiusMarker { get; set; }
            public Vector3 Position { get; set; }
            public string Tier { get; set; }
        }

        #endregion

        #region Collider Checks

        public static class CollidersChecks
        {
            public static bool InsideRock(Vector3 position, float radius)
            {
                List<Collider> colliders = Pool.GetList<Collider>();

                Vis.Colliders(position, radius, colliders, Layers.Mask.World, 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 GetTerrainInfo(Vector3 position, out RaycastHit hitInfo)
            {
                return Physics.Linecast(position + Vector3.up, position - Vector3.up, out hitInfo, Layers.Mask.Terrain);
            }

            public static bool InWater(Vector3 position) => WaterLevel.Test(position, false, false);

            public static bool OnRoadOrRail(Vector3 position) => (TerrainMeta.TopologyMap.GetTopology(position) & (int)TerrainTopology.Enum.Road) != 0;

            public static bool InNoBuildZone(Vector3 position, float radius) => Physics.CheckSphere(position, radius, Layers.Mask.Prevent_Building, QueryTriggerInteraction.Ignore);
        }

        #endregion

        #region Turret MonoBehaviour

        public class TurretMonoBehaviour : 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 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
    }
}