using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("IndustrialRecycler", "Marte6", "1.0.0")]
    [Description("Automate recycling by attaching storage and adaptors to recyclers.")]
    class IndustrialRecycler : RustPlugin
    {
        #region Fields
        private static IndustrialRecycler _instance;
        private PluginConfig _config;
        private const string RecyclerPrefabPath = "assets/bundled/prefabs/static/recycler_static.prefab";
        private const string StorageInputPrefabPath = "assets/content/vehicles/boats/rowboat/subents/rowboat_storage.prefab";
        private const string StorageOutputPrefabPath = "assets/prefabs/deployable/hot air balloon/subents/hab_storage.prefab";
        private const string AdaptorPrefabPath = "assets/prefabs/deployable/playerioents/industrialadaptors/storageadaptor.deployed.prefab";
        private const string SmallGeneratorPrefabName = "electric.generator.small";
        private const ulong RecyclerSkinID = 3341430953;
        #endregion

        #region Cache
        private readonly Dictionary<ulong, ulong> _recyclersPlayers = new();
        private readonly HashSet<ulong> _itemContainersRecycler = new();
        private readonly HashSet<ulong> _itemContainersIn = new();
        private readonly HashSet<ulong> _itemContainersOut = new();
        private readonly HashSet<ulong> _adaptors = new();
        #endregion

        #region Game Hooks
        private void Init()
        {
            foreach (var command in _config.Commands)
            {
                cmd.AddChatCommand(command, this, nameof(HandleTestCommand));
            }
        }

        private void OnServerInitialized()
        {
            _instance = this;

            foreach (var recycler in UnityEngine.Object.FindObjectsOfType<Recycler>())
            {
                if (recycler.OwnerID != 0UL)
                {
                    recycler.gameObject.AddComponent<RecyclerManager>();
                }
            }
        }

        private void Unload()
        {
            foreach (var recycler in UnityEngine.Object.FindObjectsOfType<RecyclerManager>())
            {
                UnityEngine.Object.Destroy(recycler);
            }

            _recyclersPlayers.Clear();
            _itemContainersRecycler.Clear();
            _itemContainersIn.Clear();
            _itemContainersOut.Clear();
            _adaptors.Clear();
        }

        private void OnItemAddedToContainer(ItemContainer container, Item item)
        {
            Recycler recycler = container.entityOwner as Recycler ?? container.entityOwner?.GetParentEntity() as Recycler;
            if (recycler == null)
                return;

            var recyclerManager = recycler.GetComponent<RecyclerManager>();
            if (recyclerManager == null)
                return;

            bool isPrimaryRecycler = container.entityOwner is Recycler;
            ulong containerUid = container.uid.Value;

            if (isPrimaryRecycler && _itemContainersRecycler.Contains(containerUid))
            {
                HandleItemsInRecycler(recyclerManager, item);
            }
            else if (!isPrimaryRecycler && _itemContainersIn.Contains(containerUid))
            {
                recyclerManager.MoveItemsToRecycler();
            }
        }

        private void OnEntityBuilt(Planner planner, GameObject buildingObject)
        {
            var player = planner?.GetOwnerPlayer();
            var entity = buildingObject.GetComponent<BaseEntity>();

            if (player == null || entity == null || entity.ShortPrefabName != "generator.small" || entity.skinID != RecyclerSkinID)
                return;

            if (_recyclersPlayers.Count(c => c.Value == player.userID) >= _config.Limit)
            {
                player.ChatMessage("You have reached the recycler limit.");
                timer.Once(1, () => ProvideRecyclerToPlayer(player));
                NextTick(() => entity?.Kill());
                return;
            }

            if (!DeployRecyclerSetup(player, entity))
            {
                player.ChatMessage("Recycler must be placed on a construction block.");
                timer.Once(1, () => ProvideRecyclerToPlayer(player));
            }

            NextTick(() => entity?.Kill());
        }

        private object CanPickupEntity(BasePlayer player, IndustrialStorageAdaptor entity)
        {
            if (_adaptors.Contains(entity.net.ID.Value))
            {
                entity.GetParentEntity()?.GetParentEntity()?.Kill();
                ProvideRecyclerToPlayer(player);
                return false;
            }

            return null;
        }

        private object OnEntityKill(Recycler recycler)
        {
            _recyclersPlayers.Remove(recycler.net.ID.Value);
            return null;
        }
        #endregion

        #region Main
        private void HandleTestCommand(BasePlayer player, string command, string[] args)
        {
            ProvideRecyclerToPlayer(player);
        }

        private void ProvideRecyclerToPlayer(BasePlayer player)
        {
            var recyclerItem = ItemManager.CreateByName(SmallGeneratorPrefabName, 1, RecyclerSkinID);
            recyclerItem.name = "Recycler";
            player.inventory.GiveItem(recyclerItem);
        }

        private bool DeployRecyclerSetup(BasePlayer player, BaseEntity entity)
        {
            var foundationBlock = DetectSupportingFoundation(entity);
            if (foundationBlock == null)
                return false;

            var spawnedRecycler = SpawnRecyclerWithComponents(player, entity, foundationBlock);
            if (spawnedRecycler == null)
                return false;

            spawnedRecycler.gameObject.AddComponent<RecyclerManager>();
            return true;
        }

        private BuildingBlock DetectSupportingFoundation(BaseEntity entity)
        {
            Vector3 origin = entity.transform.position + Vector3.up * 0.1f;
            if (Physics.Raycast(origin, Vector3.down, out RaycastHit hitInfo, 4f, LayerMask.GetMask("Construction")))
            {
                return hitInfo.GetEntity() as BuildingBlock;
            }
            return null;
        }

        private Recycler SpawnRecyclerWithComponents(BasePlayer player, BaseEntity entity, BuildingBlock foundationBlock)
        {
            var recycler = SpawnEntity(RecyclerPrefabPath, foundationBlock, player, entity.transform.position, entity.transform.rotation * Quaternion.Euler(0, 270, 0)) as Recycler;
            if (recycler == null)
                return null;

            var inputStorage = SpawnEntity(StorageInputPrefabPath, recycler, player, new Vector3(-1.0f, 0.72f, 0.31f), Quaternion.Euler(90, 90, 180));
            SpawnEntity(AdaptorPrefabPath, inputStorage, player, new Vector3(0.00f, -0.15f, -0.25f), Quaternion.Euler(180, 90, 270));
            var outputStorage = SpawnEntity(StorageOutputPrefabPath, recycler, player, new Vector3(-0.9f, 0.72f, -0.09f), Quaternion.Euler(180, 90, 180));
            SpawnEntity(AdaptorPrefabPath, outputStorage, player, new Vector3(0.00f, 0.26f, -0.05f), Quaternion.Euler(0, 90, 0));

            return recycler;
        }

        private BaseEntity SpawnEntity(string prefabPath, BaseEntity parent, BasePlayer player, Vector3 position, Quaternion rotation)
        {
            var entity = GameManager.server.CreateEntity(prefabPath, Vector3.zero, Quaternion.identity, true);
            if (entity == null)
                return null;

            entity.OwnerID = player.userID;

            if (parent != null)
            {
                entity.SetParent(parent, true);
            }

            if (parent is BuildingBlock)
            {
                entity.transform.position = position;
                entity.transform.rotation = rotation;
            }
            else
            {
                entity.transform.localPosition = position;
                entity.transform.localRotation = rotation;
            }

            entity.Spawn();
            entity.SendNetworkUpdateImmediate();
            return entity;
        }

        private void HandleItemsInRecycler(RecyclerManager recyclerManager, Item item)
        {
            if (item.position < 6)
            {
                recyclerManager.Recycler.StartRecycling();
            }
            else
            {
                if (!item.MoveToContainer(recyclerManager.OutputContainer.inventory))
                {
                    recyclerManager.Recycler.StopRecycling();
                }
            }
        }
        #endregion

        #region Configs
        private class PluginConfig
        {
            [JsonProperty("Commands")]
            public List<string> Commands { get; set; }

            [JsonProperty("Limit Per Player")]
            public int Limit { get; set; }

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

        protected override void LoadConfig()
        {
            base.LoadConfig();

            try
            {
                _config = Config.ReadObject<PluginConfig>() ?? throw new Exception();

                if (_config.Version < Version)
                {
                    UpdateConfiguration();
                }

                SaveConfig();
            }
            catch
            {
                PrintError("There is an error in your configuration file. Default values will be used.");
                LoadDefaultConfig();
            }
        }

        protected override void LoadDefaultConfig()
        {
            _config = CreateDefaultConfig();
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(_config);
        }

        private void UpdateConfiguration()
        {
            PrintWarning("Configuration change detected! Updating settings.");
            _config.Version = Version;
            PrintWarning("Configuration update completed!");
        }

        private PluginConfig CreateDefaultConfig()
        {
            return new PluginConfig
            {
                Commands = new List<string> { "teste", "testee", "testeeee" },
                Limit = 1,
                Version = Version,
            };
        }
        #endregion

        #region MonoBehaviour
        private class RecyclerManager : MonoBehaviour
        {
            public Recycler Recycler { get; private set; }
            public StorageContainer InputContainer { get; private set; }
            public StorageContainer OutputContainer { get; private set; }

            private const int RecyclerSlotCapacity = 48;
            private const float ItemMoveInterval = 10f; // Adjusted interval for efficiency

            private void Awake()
            {
                InitializeComponents();
                ValidateComponents();
                RegisterEntities();
                ConfigureRecycler();

                InvokeRepeating(nameof(MoveItemsToRecycler), 0f, ItemMoveInterval);
            }

            private void InitializeComponents()
            {
                Recycler = GetComponent<Recycler>();
                InputContainer = GetStorageContainer(StorageInputPrefabPath);
                OutputContainer = GetStorageContainer(StorageOutputPrefabPath);
            }

            private StorageContainer GetStorageContainer(string containerName)
            {
                return Recycler.children.OfType<StorageContainer>().FirstOrDefault(c => c?.name == containerName);
            }

            private void ValidateComponents()
            {
                if (InputContainer == null || OutputContainer == null)
                {
                    Destroy(this);
                    _instance.PrintError("Storage containers missing.");
                    return;
                }
            }

            private void RegisterEntities()
            {
                _instance._itemContainersIn.Add(InputContainer.inventory.uid.Value);
                _instance._itemContainersOut.Add(OutputContainer.inventory.uid.Value);
                _instance._itemContainersRecycler.Add(Recycler.inventory.uid.Value);

                var adaptorIn = InputContainer.children.OfType<IndustrialStorageAdaptor>().FirstOrDefault();
                if (adaptorIn != null)
                    _instance._adaptors.Add(adaptorIn.net.ID.Value);

                var adaptorOut = OutputContainer.children.OfType<IndustrialStorageAdaptor>().FirstOrDefault();
                if (adaptorOut != null)
                    _instance._adaptors.Add(adaptorOut.net.ID.Value);
            }

            private void ConfigureRecycler()
            {
                _instance._recyclersPlayers[Recycler.net.ID.Value] = Recycler.OwnerID;
                InputContainer.inventory.capacity = RecyclerSlotCapacity;
                OutputContainer.inventory.capacity = RecyclerSlotCapacity;
            }

            public void MoveItemsToRecycler()
            {
                // Calculate the number of available slots in the recycler
                int availableRecyclerSlots = 6 - Recycler.inventory.itemList.Count;

                // Check if the output container is full
                bool isOutputFull = OutputContainer.inventory.IsFull();

                // If the output is full, stop the recycler and return early
                if (isOutputFull)
                {
                    Recycler.StopRecycling();
                    return;
                }

                // Move items in the recycler beyond the first six slots to the output
                for (int i = 6; i < Recycler.inventory.capacity; i++)
                {
                    var item = Recycler.inventory.GetSlot(i);
                    if (item != null)
                    {
                        // Attempt to move each item to the output container
                        if (item.MoveToContainer(OutputContainer.inventory))
                        {
                            availableRecyclerSlots++;
                        }
                    }
                }

                // Get a snapshot of items currently in the input container to avoid modifying the collection during iteration
                List<Item> itemsToMove = new List<Item>(InputContainer.inventory.itemList);

                foreach (var item in itemsToMove)
                {
                    if (item.info.Blueprint != null)
                    {
                        // Only attempt to move recyclable items if there are available slots and the output is not full
                        if (availableRecyclerSlots > 0)
                        {
                            for (int slot = 0; slot < 6; slot++)
                            {
                                if (Recycler.inventory.GetSlot(slot) == null)
                                {
                                    if (item.MoveToContainer(Recycler.inventory, slot))
                                    {
                                        availableRecyclerSlots--; // Decrease available slots count after a successful move
                                        break; // Move to the next item after successfully moving this one
                                    }
                                }
                            }
                        }
                        // Recyclable items stay in the input container if the recycler is full
                    }
                    else
                    {
                        // Move non-recyclable items directly to the output container
                        item.MoveToContainer(OutputContainer.inventory);
                    }
                }

                // Start recycling if the recycler is not full and the output is not too
                if (!isOutputFull && Recycler.inventory.itemList.Count > 0)
                {
                    Recycler.StartRecycling();
                }
            }

            private void OnDestroy()
            {
                CancelInvoke(nameof(MoveItemsToRecycler));
            }
        }
        #endregion
    }
}