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;

namespace Oxide.Plugins
{
    [Info("Dungeons", "Marte6", "1.0.0")]
    public class Dungeons : RustPlugin
    {
        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;

            AutoSpawnDungeon(player);
        }

        [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()
        {
            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(NPCAutoTurret),
                    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 || victim.IsDestroyed || !victim.isSpawned)
                return null;

            if (victim == null || victim.IsDestroyed || !victim.isSpawned || hitInfo == null || 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,
                () =>
                {
                    AutoSpawnDungeon();
                }
            );
        }

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

        private void AutoSpawnDungeon(BasePlayer player = null)
        {
            if (_dungeonData.ActiveDungeons.Count >= _configData.DungeonSpawn.MaxTotalActiveDungeons)
            {
                if (_configData.EnableDebug)
                    Puts(lang.GetMessage("MaxActiveDungeonsReached", this));

                player?.ChatMessage(Msg(player.UserIDString, "MaxActiveDungeonsReached"));
            }
            else 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);
                    var grid = PhoneController.PositionToGridCoord(position);

                    if (_configData.EnableDebug)
                        Puts(Msg(null, "DungeonSpawnedSuccess", grid));

                    player?.ChatMessage(Msg(player.UserIDString, "DungeonSpawnedSuccess", grid));
                }
            }
            else
            {
                if (_configData.EnableDebug)
                    Puts(lang.GetMessage("DungeonSpawnFailed", this));

                player?.ChatMessage(Msg(player.UserIDString, "DungeonSpawnFailed"));
            }
        }

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

            if (!(GameManager.server.CreateEntity(prefabPath, position, rotation) is BasePortal dungeonPortal))
                return null;

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

            var proceduralDungeon = GetProceduralDungeon(dungeonPortal);
            if (proceduralDungeon == null)
            {
                Puts("Procedural Dungeon is null. Destroying the Dungeon.");
                return null;
            }

            if (proceduralDungeon.spawnedCells.Count < 1)
            {
                Puts("Spawned cells count is zero. Destroying the Dungeon.");
                dungeonPortal.Kill(BaseNetworkable.DestroyMode.None);
                return null;
            }

            var spawnLocations = GetAvailableSpawnLocations(proceduralDungeon);
            if (spawnLocations.Count == 0)
            {
                Puts("No valid spawn locations available inside Dungeon. Destroying the Dungeon.");
                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>(),
            };

            timer.Once(
                3,
                () =>
                {
                    RemoveOriginalEntities(proceduralDungeon);
                    PopulateDungeon(selectedTier, activeDungeon, spawnLocations);

                    _dungeonData.ActiveDungeons.Add(activeDungeon);
                }
            );

            return selectedTier;
        }

        private ProceduralDynamicDungeon GetProceduralDungeon(BasePortal dungeonPortal)
        {
            return (dungeonPortal as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeonPortal as HalloweenDungeon)?.dungeonInstance.Get(true);
        }

        private void PopulateDungeon(DungeonTierConfig tierConfig, ActiveDungeon activeDungeon, List<(Vector3 Position, Quaternion Rotation)> spawnLocations)
        {
            var allSpawnEntries = GenerateSpawnEntries(tierConfig);
            int spawnIndex = 0;

            foreach (var entry in allSpawnEntries)
            {
                BaseEntity entity;
                var (position, rotation) = spawnLocations[spawnIndex];
                Puts(entry.prefab.resourcePath);

                if (entry.prefab.resourcePath == "assets/prefabs/npc/autoturret/autoturret_deployed.prefab")
                {
                    entity = GameManager.server.CreateEntity(entry.prefab.resourcePath, position, rotation) as NPCAutoTurret;
                }
                else
                {
                    entity = GameManager.server.CreateEntity(entry.prefab.resourcePath, position, rotation);
                }

                if (entity is AutoTurret)
                {
                    entity = entity as NPCAutoTurret;
                    Puts("AutoTurret");
                }

                if (entity is NPCAutoTurret)
                {
                    Puts("NPCAutoTurret");
                }

                if (entity != null)
                {
                    InitializeEntity(entity, tierConfig, activeDungeon);
                }

                spawnIndex = (spawnIndex + 1) % spawnLocations.Count;
            }

            if (_configData.EnableDebug)
                Puts($"Placed {activeDungeon.EntityIds.Count}/{allSpawnEntries.Count} entities.");
        }

        private List<(Vector3 Position, Quaternion Rotation)> GetAvailableSpawnLocations(ProceduralDynamicDungeon proceduralDungeon)
        {
            var locations = new List<(Vector3, Quaternion)>();

            foreach (var cell in proceduralDungeon.spawnedCells)
            {
                foreach (var group in cell.spawnGroups)
                {
                    foreach (var spawnPoint in group.spawnPoints)
                    {
                        if (spawnPoint != null && spawnPoint.IsAvailableTo(null))
                        {
                            spawnPoint.GetLocation(out var position, out var rotation);
                            locations.Add((position, rotation));
                        }
                    }
                }
            }
            return locations;
        }

        private void InitializeEntity(BaseEntity entity, DungeonTierConfig tierConfig, ActiveDungeon activeDungeon)
        {
            entity.OwnerID = OwnerID;
            entity.Spawn();

            if (entity is NPCPlayer)
            {
                activeDungeon.NpcIds.Add(entity.net.ID.Value);
            }
            else if (entity is NPCAutoTurret turret)
            {
                ConfigureTurret(turret, tierConfig.AutoTurretConfig);
            }
            else if (entity is StorageContainer storage)
            {
                ConfigureStorage(storage);
            }

            activeDungeon.EntityIds.Add(entity.net.ID.Value);
        }

        private void RemoveOriginalEntities(ProceduralDynamicDungeon proceduralDungeon)
        {
            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))
                        {
                            if (_configData.EnableDebug)
                                Puts($"Removing original entity: {entity.ShortPrefabName}");

                            entity.Kill();
                        }
                    }
                }
            }
        }

        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 ConfigureTurret(NPCAutoTurret turret, TurretConfig turretConfig)
        {
            turret.health = turretConfig.Health;
            turret.SetMaxHealth(turretConfig.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();
            turret.gameObject.AddComponent<TurretMonoBehaviour>();
        }

        public class TurretMonoBehaviour : MonoBehaviour
        {
            private NPCAutoTurret _turret;

            private void Awake()
            {
                _turret = GetComponent<NPCAutoTurret>();

                _turret.SetPeacekeepermode(false);
                _turret.InitiateStartup();
                //_turret.SetIsOnline(true);
                _turret.isLootable = false;
                _turret.dropFloats = false;
                _turret.dropsLoot = false;

                //InvokeRepeating(nameof(RefillAmmo), 2f, 30f);
            }

            private void RefillAmmo()
            {
                int maxReserveAmmo = 1000;

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

                var ammoType = baseProjectile.primaryMagazine.ammoType;
                int currentAmmoCount = _turret.inventory.itemList.Where(item => item.info == ammoType).Sum(item => item.amount);

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

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

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

        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, string messageKey)
        {
            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 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 IsValidPosition(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 (IsValidPosition(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("Enable Debug")]
            public bool EnableDebug { get; set; } = true;

            [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 Auto Spawn")]
            public bool EnableAutoSpawn { get; set; } = true;

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

            [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;
        }

        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 at grid {0}!",
                    ["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
    }
}