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

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

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

        #region Initialization

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

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

        private void RemoveAllExistingDungeons()
        {
            var existingXmasDungeons = UnityEngine.Object.FindObjectsOfType<XmasDungeon>();
            foreach (var dungeon in existingXmasDungeons)
            {
                Puts($"Removing existing Xmas Dungeon at position {dungeon.transform.position}");
                dungeon.Kill(BaseNetworkable.DestroyMode.None);
            }

            var existingHalloweenDungeons = UnityEngine.Object.FindObjectsOfType<HalloweenDungeon>();
            foreach (var dungeon in existingHalloweenDungeons)
            {
                Puts($"Removing existing Halloween Dungeon at position {dungeon.transform.position}");
                dungeon.Kill(BaseNetworkable.DestroyMode.None);
            }
        }

        #endregion

        #region Hooks

        private object CanEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (!IsDungeonCreatedEntity(entity))
            {
                return null;
            }

            return true;
        }

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

            BaseEntity attacker = info.Initiator;

            if (IsDungeonCreatedEntity(victim) && attacker is AutoTurret turret && IsDungeonCreatedEntity(turret))
            {
                return false;
            }

            return null;
        }

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

        #endregion

        #region Dungeon Management

        [ChatCommand("removedun")]
        private void RemoveDungeon()
        {
            RemoveExistingDungeon();

            Puts("Dungeon removed.");
        }

        [ChatCommand("dun")]
        private void AutoSpawnDungeon()
        {
            RemoveExistingDungeon();

            foreach (var player in BasePlayer.activePlayerList)
            {
                if (!TryFindDungeonSpawnPoint(player.transform.position, out var position, out var rotation))
                    continue;

                var selectedTier = CreateDungeon(position, rotation);
                if (selectedTier != null)
                {
                    NotifyPlayersOfDungeonLocation(position, selectedTier);
                    return;
                }
            }

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

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

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

        private DungeonTierConfig CreateDungeon(Vector3 position, Quaternion rotation)
        {
            string chosenPrefabPath = RandomlySelectDungeonPrefab();
            BasePortal dungeon = null;

            if (string.IsNullOrEmpty(chosenPrefabPath))
            {
                Puts("Failed to select a valid dungeon prefab.");
                return null;
            }

            Puts($"Attempting to create a dungeon. Prefab: {chosenPrefabPath}");

            if (chosenPrefabPath == XmasDungeonPrefabPath)
            {
                dungeon = GameManager.server.CreateEntity(XmasDungeonPrefabPath, position, rotation) as XmasDungeon;
            }
            else if (chosenPrefabPath == HalloweenDungeonPrefabPath)
            {
                dungeon = GameManager.server.CreateEntity(HalloweenDungeonPrefabPath, position, rotation) as HalloweenDungeon;
            }

            if (dungeon != null)
            {
                dungeon.Spawn();
                Puts("Dungeon spawned, checking cell count...");

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

                if (proceduralDungeon != null)
                {
                    Puts($"Current dungeon cell count: {proceduralDungeon.spawnedCells.Count}");

                    if (proceduralDungeon.spawnedCells.Count >= 1)
                    {
                        Puts("Dungeon is valid with sufficient cells, determining tier.");

                        DungeonTierConfig selectedTier = DetermineDungeonTier(proceduralDungeon.spawnedCells.Count);
                        IntegrateEntitiesIntoDungeon(dungeon, selectedTier);
                        _currentDungeon = dungeon;
                        return selectedTier;
                    }
                    else
                    {
                        Puts("Insufficient cells in dungeon, destroying dungeon.");
                        RemoveDungeonEntities();
                        dungeon.Kill(BaseNetworkable.DestroyMode.None);
                    }
                }
                else
                {
                    Puts("Failed to retrieve procedural dungeon instance, destroying dungeon.");
                    RemoveDungeonEntities();
                    dungeon.Kill(BaseNetworkable.DestroyMode.None);
                }
            }
            else
            {
                Puts("Failed to create dungeon entity.");
            }

            return null;
        }

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

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

        private XmasDungeon CreateXmasDungeon(Vector3 position, Quaternion rotation, DungeonTierConfig tierConfig)
        {
            var dungeon = GameManager.server.CreateEntity(XmasDungeonPrefabPath, position, rotation) as XmasDungeon;

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

            dungeon.Spawn();
            IntegrateEntitiesIntoDungeon(dungeon, tierConfig);

            return dungeon;
        }

        private HalloweenDungeon CreateHalloweenDungeon(Vector3 position, Quaternion rotation, DungeonTierConfig tierConfig)
        {
            var dungeon = GameManager.server.CreateEntity(HalloweenDungeonPrefabPath, position, rotation) as HalloweenDungeon;

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

            dungeon.Spawn();
            IntegrateEntitiesIntoDungeon(dungeon, tierConfig);

            return dungeon;
        }

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

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

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

            if (availablePrefabs.Count == 0)
                return null;

            return availablePrefabs[Random.Range(0, availablePrefabs.Count)];
        }

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

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

            int cellCount = proceduralDungeon.spawnedCells.Count;
            if (cellCount == 0)
            {
                Puts("No cells found in dungeon for entities.");
                return;
            }

            var allSpawnEntries = new List<SpawnGroup.SpawnEntry>();

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

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

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

            foreach (var cell in proceduralDungeon.spawnedCells)
            {
                SetupSpawnGroup(cell, allSpawnEntries, tierConfig);
            }
        }

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

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

                    if (entity is AutoTurret autoTurret)
                    {
                        ConfigureAutoTurret(autoTurret, tierConfig.AutoTurretConfig);
                        TurretComponent.Configure(autoTurret, 30f);
                    }

                    if (entity is StorageContainer box)
                    {
                        box.skinID = _configData.LootConfig.SmallWoodBoxSkinID;
                        AddLockToBox(box);
                        FillLootBox(box.inventory, _configData.LootConfig.LootItems);
                    }
                }
            }
        }

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

        private void FillLootBox(ItemContainer container, List<ItemConfig> lootItems)
        {
            var shuffledItems = lootItems.OrderBy(x => Random.Range(0f, 1f)).ToList();

            int maxDifferentItems = _configData.LootConfig.MaxDifferentItemsPerBox;
            int differentItemsCount = 0;

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

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

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

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

            var weapon = ItemManager.CreateByName(turretConfig.WeaponShortName);
            if (weapon != null)
            {
                foreach (var attachmentShortName in turretConfig.AttachmentShortNames)
                {
                    var attachment = ItemManager.CreateByName(attachmentShortName);
                    attachment?.MoveToContainer(weapon.contents);
                }
                weapon.MoveToContainer(autoTurret.inventory, 0);

                autoTurret.UpdateAttachedWeapon();
            }

            foreach (var reserveAmmoInfo in turretConfig.ReserveAmmo)
            {
                var ammoItem = ItemManager.CreateByName(reserveAmmoInfo.ShortName, Random.Range(reserveAmmoInfo.MinimumAmount, reserveAmmoInfo.MaximumAmount + 1));
                ammoItem.MoveToContainer(autoTurret.inventory);
            }

            if (turretConfig.ClipAmmo != null)
            {
                var loadedAmmoItem = ItemManager.CreateByName(turretConfig.ClipAmmo.ShortName, Random.Range(turretConfig.ClipAmmo.MinimumAmount, turretConfig.ClipAmmo.MaximumAmount + 1));
                BaseProjectile heldEntity = weapon.GetHeldEntity() as BaseProjectile;
                heldEntity.primaryMagazine.ammoType = loadedAmmoItem.info;
            }

            autoTurret.UpdateTotalAmmo();
            autoTurret.EnsureReloaded();
            autoTurret.SetPeacekeepermode(false);
            autoTurret.InitiateStartup();
            autoTurret.SetIsOnline(true);
            autoTurret.SendNetworkUpdateImmediate();
        }

        private Vector3 GetRandomPositionNearby(Vector3 center, float minRadius, float maxRadius)
        {
            Vector2 randomCircle = Random.insideUnitCircle * Random.Range(minRadius, maxRadius);
            return new Vector3(center.x + randomCircle.x, center.y, center.z + randomCircle.y);
        }

        #endregion

        #region Utility and Cleanup

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

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

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

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

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

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

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

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

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

            return false;
        }

        private void RemoveExistingDungeon()
        {
            if (_currentDungeon is BaseEntity dungeon)
            {
                Puts("Removing existing dungeon...");

                foreach (var entity in _spawnedEntities)
                {
                    if (entity != null && !entity.IsDestroyed)
                    {
                        entity.Kill(BaseNetworkable.DestroyMode.None);
                        Puts($"Entity {entity.ShortPrefabName} destroyed.");
                    }
                }

                _spawnedEntities.Clear();

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

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

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

        #endregion

        #region Terrain Utilities

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

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

                return randomPosition;
            }

            public static bool InsideRock(Vector3 position, float radius)
            {
                var colliders = Pool.GetList<Collider>();
                Vis.Colliders(position, radius, colliders, Layers.Mask.World, QueryTriggerInteraction.Ignore);
                bool result = 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 result;
            }

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

            public static bool HasEntityNearby(Vector3 position, float radius, int mask, string prefabName = null)
            {
                var hitColliders = Pool.GetList<Collider>();
                GamePhysics.OverlapSphere(position, radius, hitColliders, mask, QueryTriggerInteraction.Ignore);
                bool result = hitColliders.Any(collider =>
                {
                    BaseEntity entity = collider.gameObject.ToBaseEntity();
                    return entity != null && (prefabName == null || entity.PrefabName == prefabName);
                });
                Pool.FreeList(ref hitColliders);
                return result;
            }

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

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

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

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

        #endregion

        #region Turret Component

        internal class TurretComponent : FacepunchBehaviour
        {
            private AutoTurret turretEntity;
            private float detectionRadius;

            internal static void Configure(AutoTurret turretEntity, float detectionRadius)
            {
                var component = turretEntity.gameObject.AddComponent<TurretComponent>();
                component.Initialize(turretEntity, detectionRadius);
            }

            private void Initialize(AutoTurret turretEntity, float detectionRadius)
            {
                this.turretEntity = turretEntity;
                this.detectionRadius = detectionRadius;

                var triggerCollider = turretEntity.targetTrigger.GetComponent<SphereCollider>();
                triggerCollider.enabled = false;

                turretEntity.Invoke(
                    () =>
                    {
                        turretEntity.CancelInvoke(turretEntity.ServerTick);
                        turretEntity.SetTarget(null);
                    },
                    1.1f
                );
                turretEntity.InvokeRepeating(new Action(TurretTick), Random.Range(1.2f, 2.2f), 0.015f);
                turretEntity.InvokeRepeating(SearchTargets, 3f, 1f);
            }

            private void SearchTargets()
            {
                if (turretEntity.targetTrigger.entityContents == null)
                    turretEntity.targetTrigger.entityContents = new HashSet<BaseEntity>();
                else
                    turretEntity.targetTrigger.entityContents.Clear();

                int found = BaseEntity.Query.Server.GetPlayersInSphereFast(transform.position, detectionRadius, AIBrainSenses.playerQueryResults, IsValidTarget);

                if (found == 0)
                    return;

                turretEntity.authDirty = true;

                for (int i = 0; i < found; i++)
                {
                    var player = AIBrainSenses.playerQueryResults[i];

                    if (Interface.CallHook("OnEntityEnter", turretEntity.targetTrigger, player) != null)
                        continue;

                    if (player.IsSleeping() || (player.InSafeZone() && !player.IsHostile()))
                        continue;

                    turretEntity.targetTrigger.entityContents.Add(player);
                }
            }

            private bool IsValidTarget(BasePlayer player)
            {
                return player != null && !player.IsNpc;
            }

            private void TurretTick()
            {
                if (turretEntity.isClient || turretEntity.IsDestroyed)
                    return;

                float timeTick = (float)turretEntity.timeSinceLastServerTick;
                turretEntity.timeSinceLastServerTick = 0;

                if (!turretEntity.IsOnline())
                {
                    turretEntity.OfflineTick();
                }
                else if (!turretEntity.IsBeingControlled)
                {
                    if (!turretEntity.HasTarget())
                    {
                        turretEntity.IdleTick(timeTick);
                    }
                    else
                    {
                        TargetTick();
                    }
                }

                turretEntity.UpdateFacingToTarget(timeTick);

                if (turretEntity.totalAmmoDirty && Time.time > turretEntity.nextAmmoCheckTime)
                {
                    turretEntity.UpdateTotalAmmo();
                    turretEntity.totalAmmoDirty = false;
                    turretEntity.nextAmmoCheckTime = Time.time + 0.5f;
                }
            }

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

                turretEntity.EnsureReloaded();
                BaseProjectile weapon = turretEntity.GetAttachedWeapon();

                if (
                    Time.time >= turretEntity.nextShotTime
                    && turretEntity.targetVisible
                    && Mathf.Abs(turretEntity.AngleToTarget(turretEntity.target, turretEntity.currentAmmoGravity != 0f)) < turretEntity.GetMaxAngleForEngagement()
                )
                {
                    if (weapon)
                    {
                        if (weapon.primaryMagazine.contents > 0)
                        {
                            turretEntity.FireAttachedGun(turretEntity.AimOffset(turretEntity.target), turretEntity.aimCone, null, turretEntity.PeacekeeperMode() ? turretEntity.target : null);
                            float delay = weapon.isSemiAuto ? weapon.repeatDelay * 1.5f : weapon.repeatDelay;
                            delay = weapon.ScaleRepeatDelay(delay);
                            turretEntity.nextShotTime = Time.time + delay;
                        }
                        else
                        {
                            turretEntity.nextShotTime = Time.time + 5f;
                        }
                    }
                    else if (turretEntity.HasFallbackWeapon())
                    {
                        turretEntity.FireGun(turretEntity.AimOffset(turretEntity.target), turretEntity.aimCone, null, turretEntity.target);
                        turretEntity.nextShotTime = Time.time + 0.115f;
                    }
                    else if (turretEntity.HasGenericFireable())
                    {
                        turretEntity.AttachedWeapon.ServerUse();
                        turretEntity.nextShotTime = Time.time + 0.115f;
                    }
                    else
                    {
                        turretEntity.nextShotTime = Time.time + 1f;
                    }
                }

                var targetPlayer = turretEntity.target as BasePlayer;

                if (
                    turretEntity.target != null
                    && (
                        !IsRealPlayer(targetPlayer)
                        || turretEntity.target.IsDead()
                        || Time.realtimeSinceStartup - turretEntity.lastTargetSeenTime > 3f
                        || Vector3.Distance(turretEntity.transform.position, turretEntity.target.transform.position) > turretEntity.sightRange
                        || (turretEntity.PeacekeeperMode() && !turretEntity.IsEntityHostile(turretEntity.target))
                    )
                )
                    turretEntity.SetTarget(null);
            }

            public static bool IsRealPlayer(BasePlayer player) => player != null && player.userID.IsSteamId();
        }

        #endregion

        #region Configuration

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

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

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

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

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

        private class DungeonTiers
        {
            [JsonProperty(PropertyName = "Easy")]
            public DungeonTierConfig Easy { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/tunneldweller/npc_tunneldweller.prefab", EntitysPerCell = 1 },
                    },
                };

            [JsonProperty(PropertyName = "Normal")]
            public DungeonTierConfig Normal { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/tunneldweller/npc_tunneldweller.prefab", EntitysPerCell = 1 },
                    },
                };

            [JsonProperty(PropertyName = "Medium")]
            public DungeonTierConfig Medium { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/tunneldweller/npc_tunneldweller.prefab", EntitysPerCell = 1 },
                    },
                };

            [JsonProperty(PropertyName = "Hard")]
            public DungeonTierConfig Hard { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.prefab", EntitysPerCell = 1 },
                    },
                };

            [JsonProperty(PropertyName = "Nightmare")]
            public DungeonTierConfig Nightmare { get; set; } =
                new DungeonTierConfig
                {
                    Enabled = true,
                    NpcSpawnConfigs = new List<NpcSpawnConfig>
                    {
                        new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.prefab", EntitysPerCell = 1 },
                    },
                };
        }

        private class DungeonTierConfig
        {
            [JsonProperty(PropertyName = "Enabled")]
            public bool Enabled { 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 LootConfig
        {
            [JsonProperty(PropertyName = "Loot Boxes per Cell")]
            public int LootBoxesPerCell { get; set; } = 1;

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

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

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

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

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

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

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

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

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

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

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

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

            [JsonProperty(PropertyName = "Entitys Per Cell")]
            public int EntitysPerCell { get; set; }
        }

        private class TurretConfig
        {
            [JsonProperty(PropertyName = "Turrets Per Cell")]
            public int TurretsPerCell { get; set; } = 1;

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

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

            [JsonProperty(PropertyName = "Clip Ammo")]
            public ItemConfig ClipAmmo { get; set; } =
                new ItemConfig
                {
                    ShortName = "ammo.rifle",
                    MinimumAmount = 30,
                    MaximumAmount = 30,
                };

            [JsonProperty(PropertyName = "Reserve Ammo", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<ItemConfig> ReserveAmmo { get; set; } =
                new List<ItemConfig>
                {
                    new ItemConfig
                    {
                        ShortName = "ammo.rifle",
                        MinimumAmount = 128,
                        MaximumAmount = 128,
                    },
                };

            [JsonProperty(PropertyName = "Attachment Short Names", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> AttachmentShortNames { get; set; } = new List<string> { "weapon.mod.lasersight" };
        }

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

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

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

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

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

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

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

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

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

        #endregion
    }
}