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 adapters to recyclers.")]
    class IndustrialRecycler : RustPlugin
    {
        #region Fields
        private static IndustrialRecycler _instance;
        private Configuration _config;
        private const string RecyclerPrefabPath = "assets/bundled/prefabs/static/recycler_static.prefab";
        private const string InputStoragePrefabPath = "assets/content/vehicles/boats/rowboat/subents/rowboat_storage.prefab";
        private const string OutputStoragePrefabPath = "assets/prefabs/deployable/hot air balloon/subents/hab_storage.prefab";
        private const string AdapterPrefabPath = "assets/prefabs/deployable/playerioents/industrialadaptors/storageadaptor.deployed.prefab";
        private const string SmallGeneratorItemName = "electric.generator.small";
        private const ulong RecyclerSkinId = 3341430953;
        #endregion

        #region Cache
        private readonly Dictionary<ulong, ulong> _recyclerOwners = new();
        private readonly HashSet<ulong> _recyclerItemContainers = new();
        private readonly HashSet<ulong> _inputItemContainers = new();
        private readonly HashSet<ulong> _storageAdapters = new();
        #endregion

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

        private void OnServerInitialized()
        {
            _instance = this;

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

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

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

            var recyclerComponent = recycler.GetComponent<RecyclerComponent>();
            if (recyclerComponent == null)
                return;

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

            if (isMainRecycler && _recyclerItemContainers.Contains(containerUid))
            {
                ProcessRecyclerItems(recyclerComponent, item);
            }
            else if (!isMainRecycler && _inputItemContainers.Contains(containerUid))
            {
                recyclerComponent.HandleItemTransfers();
            }
        }

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

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

            if (_recyclerOwners.Count(entry => entry.Value == player.userID) >= _config.MaxRecyclersPerPlayer)
            {
                player.ChatMessage("You have reached the recycler limit.");
                timer.Once(1, () => GiveRecyclerItem(player));
                NextTick(() => baseEntity?.Kill());
                return;
            }

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

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

        private object CanPickupEntity(BasePlayer player, IndustrialStorageAdaptor adapter)
        {
            if (_storageAdapters.Contains(adapter.net.ID.Value))
            {
                adapter.GetParentEntity()?.GetParentEntity()?.Kill();
                GiveRecyclerItem(player);
                return false;
            }

            return null;
        }

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

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

        private void GiveRecyclerItem(BasePlayer player)
        {
            var recyclerItem = ItemManager.CreateByName(SmallGeneratorItemName, 1, RecyclerSkinId);
            recyclerItem.name = "Recycler";
            player.inventory.GiveItem(recyclerItem);
        }

        private bool InitializeRecyclerPlacement(BasePlayer player, BaseEntity baseEntity)
        {
            var supportingFoundation = FindSupportingFoundation(baseEntity);
            if (supportingFoundation == null)
                return false;

            var spawnedRecycler = SpawnRecyclerWithComponents(player, baseEntity, supportingFoundation);
            if (spawnedRecycler == null)
                return false;

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

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

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

            var inputStorage = SpawnEntity(InputStoragePrefabPath, recycler, player, new Vector3(-1.0f, 0.72f, 0.31f), Quaternion.Euler(90, 90, 180));
            SpawnEntity(AdapterPrefabPath, inputStorage, player, new Vector3(0.00f, -0.15f, -0.25f), Quaternion.Euler(180, 90, 270));
            var outputStorage = SpawnEntity(OutputStoragePrefabPath, recycler, player, new Vector3(-0.9f, 0.72f, -0.09f), Quaternion.Euler(180, 90, 180));
            SpawnEntity(AdapterPrefabPath, 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 ProcessRecyclerItems(RecyclerComponent recyclerComponent, Item item)
        {
            if (item.position < 6)
            {
                recyclerComponent.Recycler.StartRecycling();
            }
            else
            {
                if (!item.MoveToContainer(recyclerComponent.OutputStorage.inventory))
                {
                    recyclerComponent.Recycler.StopRecycling();
                }
            }
        }
        #endregion

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

            [JsonProperty("Max Recyclers Per Player")]
            public int MaxRecyclersPerPlayer { get; set; }

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

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

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

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

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

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

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

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

        private Configuration GetDefaultConfig()
        {
            return new Configuration
            {
                Commands = new List<string> { "getrecycler", "irecycler", "giverecycler" },
                MaxRecyclersPerPlayer = 1,
                Version = Version,
            };
        }
        #endregion

        #region Localization
        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string> { ["NoPermission"] = "You don't have permission to use this command." }, this);
        }
        #endregion

        #region Component
        private class RecyclerComponent : MonoBehaviour
        {
            public Recycler Recycler { get; private set; }
            public StorageContainer InputStorage { get; private set; }
            public StorageContainer OutputStorage { get; private set; }

            private const int MaxStorageCapacity = 48;
            private const float TransferInterval = 10f;

            private void Awake()
            {
                InitializeRecyclerComponents();
                ValidateStorageContainers();
                RegisterEntities();
                SetupRecycler();

                InvokeRepeating(nameof(HandleItemTransfers), 0f, TransferInterval);
            }

            private void InitializeRecyclerComponents()
            {
                Recycler = GetComponent<Recycler>();
                InputStorage = Recycler.children.OfType<StorageContainer>().FirstOrDefault(c => c?.name == InputStoragePrefabPath);
                OutputStorage = Recycler.children.OfType<StorageContainer>().FirstOrDefault(c => c?.name == OutputStoragePrefabPath);
            }

            private void ValidateStorageContainers()
            {
                if (InputStorage == null || OutputStorage == null)
                {
                    Destroy(this);
                    _instance.PrintError("Storage containers are missing.");
                }
            }

            private void RegisterEntities()
            {
                _instance._inputItemContainers.Add(InputStorage.inventory.uid.Value);
                _instance._recyclerItemContainers.Add(Recycler.inventory.uid.Value);

                var inputAdapter = InputStorage.children.OfType<IndustrialStorageAdaptor>().FirstOrDefault();
                if (inputAdapter != null)
                    _instance._storageAdapters.Add(inputAdapter.net.ID.Value);

                var outputAdapter = OutputStorage.children.OfType<IndustrialStorageAdaptor>().FirstOrDefault();
                if (outputAdapter != null)
                    _instance._storageAdapters.Add(outputAdapter.net.ID.Value);
            }

            private void SetupRecycler()
            {
                _instance._recyclerOwners[Recycler.net.ID.Value] = Recycler.OwnerID;
                InputStorage.inventory.capacity = MaxStorageCapacity;
                OutputStorage.inventory.capacity = MaxStorageCapacity;
            }

            public void HandleItemTransfers()
            {
                int availableRecyclerSlots = 6 - Recycler.inventory.itemList.Count;

                if (OutputStorage.inventory.IsFull())
                {
                    Recycler.StopRecycling();
                    return;
                }

                TransferItemsFromRecyclerToOutput(ref availableRecyclerSlots);
                TransferItemsFromInputToRecycler(ref availableRecyclerSlots);

                if (availableRecyclerSlots < 6 && Recycler.inventory.itemList.Count > 0)
                {
                    Recycler.StartRecycling();
                }
            }

            private void TransferItemsFromRecyclerToOutput(ref int availableRecyclerSlots)
            {
                for (int slot = 6; slot < Recycler.inventory.capacity; slot++)
                {
                    var itemInRecycler = Recycler.inventory.GetSlot(slot);
                    if (itemInRecycler != null && itemInRecycler.MoveToContainer(OutputStorage.inventory))
                    {
                        availableRecyclerSlots++;
                    }
                }
            }

            private void TransferItemsFromInputToRecycler(ref int availableRecyclerSlots)
            {
                var inputItems = new List<Item>(InputStorage.inventory.itemList);

                foreach (var item in inputItems)
                {
                    if (item.info.Blueprint != null)
                    {
                        MoveItemToRecycler(item, ref availableRecyclerSlots);
                    }
                    else
                    {
                        item.MoveToContainer(OutputStorage.inventory);
                    }
                }
            }

            private void MoveItemToRecycler(Item item, ref int availableRecyclerSlots)
            {
                if (availableRecyclerSlots <= 0)
                    return;

                for (int slot = 0; slot < 6; slot++)
                {
                    if (Recycler.inventory.GetSlot(slot) == null && item.MoveToContainer(Recycler.inventory, slot))
                    {
                        availableRecyclerSlots--;
                        break;
                    }
                }
            }

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

        #endregion
    }
}