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

namespace Oxide.Plugins
{
    [Info("Turret Manager", "OrangeDoggo", "1.2.1")]
    [Description("Allows you to place down a turret fast & easy. (Auto fill up turret,Auto auth self (or/and friends)")]
    internal class TurretManager : CovalencePlugin
    {
        #region Fields

        [PluginReference]
        private Plugin Friends;

        private readonly object False = false;

        private Configuration PluginConfig;

        private const string autoFillPerm = "turretmanager.autofill";
        private const string autoAuthPerm = "turretmanager.autoauth";
        private const string autoTogglePerm = "turretmanager.autotoggle";

        private ItemDefinition ammoToLoad;
        private ItemDefinition gunToLoad;

        #endregion

        #region Hooks

        private void Init()
        {
            if (!PluginConfig.LockAutoFilledTurrets)
            {
                Unsubscribe(nameof(OnEntityDeath));
                Unsubscribe(nameof(OnTurretToggle));
                Unsubscribe(nameof(CanMoveItem));
            }
        }

        private void OnServerInitialized()
        {
            permission.RegisterPermission(autoFillPerm, this);
            permission.RegisterPermission(autoAuthPerm, this);
            permission.RegisterPermission(autoTogglePerm, this);

            gunToLoad = ItemManager.FindItemDefinition(PluginConfig.GunShortName);
            if (gunToLoad == null)
            {
                LogError("Invalid item for Gun");
                return;
            }

            if (gunToLoad.category != ItemCategory.Weapon)
            {
                LogError($"Item '{gunToLoad.shortname}' isn't a valid weapon!");
            }
            else
            {
                ammoToLoad = (ItemManager.Create(gunToLoad).GetHeldEntity() as BaseProjectile)?.primaryMagazine.ammoType;
            }
        }

        private void OnEntityBuilt(Planner plan, GameObject go)
        {
            var ownerPlayer = plan?.GetOwnerPlayer();
            if (ownerPlayer == null)
                return;

            var entity = go?.ToBaseEntity();
            if (entity == null)
                return;

            var turret = entity as AutoTurret;
            if (turret == null)
                return;

            if (HasPermission(ownerPlayer, autoAuthPerm))
            {
                if (PluginConfig.AutoAuthFriends && Friends != null)
                {
                    var friendsList = Friends.Call<string[]>("GetFriendList", ownerPlayer.userID);
                    if (friendsList != null)
                    {
                        foreach (var friendUserId in friendsList)
                        {
                            var friend = BasePlayer.Find(friendUserId);
                            if (friend == null)
                                continue;

                            if (!turret.IsAuthed(friend.userID))
                            {
                                turret.authorizedPlayers.Add(friend.userID);
                            }
                        }
                    }
                }

                if (!turret.IsAuthed(ownerPlayer.userID))
                {
                    turret.authorizedPlayers.Add(ownerPlayer.userID);
                }
            }

            if (HasPermission(ownerPlayer, autoFillPerm))
            {
                if (ammoToLoad != null && gunToLoad != null)
                {
                    var weaponItem = ItemManager.Create(gunToLoad);
                    weaponItem.MoveToContainer(turret.inventory, 0);

                    var heldWeapon = weaponItem.GetHeldEntity() as BaseProjectile;
                    if (heldWeapon != null)
                    {
                        heldWeapon.primaryMagazine.contents = 0;
                    }

                    turret.UpdateAttachedWeapon();
                    turret.CancelInvoke(turret.UpdateAttachedWeapon);

                    var weapon = turret.GetAttachedWeapon();
                    weapon.primaryMagazine.contents = weapon.primaryMagazine.capacity;

                    for (var i = 0; i < PluginConfig.AmountOfStacks; i++)
                    {
                        ItemManager.Create(ammoToLoad, PluginConfig.AmmoStackSize).MoveToContainer(turret.inventory, i + 1);
                    }

                    if (PluginConfig.LockAutoFilledTurrets)
                    {
                        turret.inventory.SetFlag(ItemContainer.Flag.IsLocked, true);
                        turret.dropsLoot = false;
                        turret.pickup.requireEmptyInv = false;
                    }
                }
                else
                {
                    LogError("{0} definition is null. Please contact the plugin author if this happens again.", ammoToLoad == null ? "Weapon ammo" : gunToLoad == null ? "Weapon" : ammoToLoad == null && gunToLoad == null ? "Weapon and Ammo" : "");
                }
            }

            if (HasPermission(ownerPlayer, autoTogglePerm))
            {
                turret.SetOnline();
                var turretSwitch = turret.GetComponentInChildren<ElectricSwitch>();
                if (turretSwitch != null)
                {
                    turretSwitch.SetSwitch(true);
                }
            }

            turret.SendNetworkUpdateImmediate();
        }

        private void OnEntityDeath(AutoTurret turret)
        {
            if (turret != null && IsTurretLocked(turret))
            {
                turret.inventory.Kill();
            }
        }

        private void OnTurretToggle(AutoTurret turret)
        {
            // Remove items if powering down while locked and out of ammo
            // Otherwise, the turret would be unusable other than picking it up
            if (turret != null && turret.IsOnline() && IsTurretLocked(turret) && GetTotalAmmo(turret) == 0)
            {
                turret.inventory.Clear();
                turret.inventory.SetFlag(ItemContainer.Flag.IsLocked, false);
            }
        }

        private object CanMoveItem(Item item)
        {
            if (item.parent == null)
                return null;

            var turret = item.parent.entityOwner as AutoTurret;
            if (turret == null)
                return null;

            // Fix issue where right-clicking an item in a locked turret inventory allows moving it
            if (item.parent.IsLocked())
                return False;

            return null;
        }

        #endregion

        #region Helper Methods

        private bool HasPermission(BasePlayer player, string perm)
        {
            return permission.UserHasPermission(player.UserIDString, perm)
                || (PluginConfig.AdminBypass && permission.UserHasGroup(player.UserIDString, "admin"));
        }

        private bool IsTurretLocked(AutoTurret turret)
        {
            return turret.inventory != null && turret.inventory.HasFlag(ItemContainer.Flag.IsLocked);
        }

        private int GetTotalAmmo(AutoTurret turret)
        {
            if (turret == null || turret.inventory == null)
                return 0;

            var weapon = turret.GetAttachedWeapon();
            if (weapon == null)
                return 0;

            // AutoTurret.GetTotalAmmo() only includes the reserve ammo, not the loaded ammo
            return weapon.primaryMagazine.contents + turret.GetTotalAmmo();
        }

        #endregion

        #region Configuration

        internal class Configuration : SerializableConfiguration
        {
            [JsonProperty("Gun (Shortname)")]
            public string GunShortName = "rifle.ak";

            [JsonProperty("Ammo amount")]
            public int AmmoStackSize = 128;

            [JsonProperty("Stack amount")]
            public int AmountOfStacks = 6;

            [JsonProperty("Lock auto filled turrets (true/false)")]
            public bool LockAutoFilledTurrets = false;

            [JsonProperty("Auto Authorize Friends (Requires Friends API)")]
            public bool AutoAuthFriends = false;

            [JsonProperty("Admin Bypass")]
            public bool AdminBypass = false;
        }

        private Configuration GetDefaultConfig() => new Configuration();

        #endregion

        #region Configuration Helpers

        internal class SerializableConfiguration
        {
            public string ToJson() => JsonConvert.SerializeObject(this);

            public Dictionary<string, object> ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
        }

        internal static class JsonHelper
        {
            public static object Deserialize(string json) => ToObject(JToken.Parse(json));

            private static object ToObject(JToken token)
            {
                switch (token.Type)
                {
                    case JTokenType.Object:
                        return token.Children<JProperty>()
                                    .ToDictionary(prop => prop.Name,
                                                  prop => ToObject(prop.Value));

                    case JTokenType.Array:
                        return token.Select(ToObject).ToList();

                    default:
                        return ((JValue)token).Value;
                }
            }
        }

        private bool MaybeUpdateConfig(Configuration config)
        {
            var currentWithDefaults = config.ToDictionary();
            var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value);
            return MaybeUpdateConfigDict(currentWithDefaults, currentRaw);
        }

        private bool MaybeUpdateConfigDict(Dictionary<string, object> currentWithDefaults, Dictionary<string, object> currentRaw)
        {
            var changed = false;

            foreach (var key in currentWithDefaults.Keys)
            {
                if (currentRaw.TryGetValue(key, out var currentRawValue))
                {
                    var currentDictValue = currentRawValue as Dictionary<string, object>;
                    if (currentWithDefaults[key] is Dictionary<string, object> defaultDictValue)
                    {
                        if (currentDictValue == null)
                        {
                            currentRaw[key] = currentWithDefaults[key];
                            changed = true;
                        }
                        else if (MaybeUpdateConfigDict(defaultDictValue, currentDictValue))
                        {
                            changed = true;
                        }
                    }
                }
                else
                {
                    currentRaw[key] = currentWithDefaults[key];
                    changed = true;
                }
            }

            return changed;
        }

        protected override void LoadDefaultConfig() => PluginConfig = GetDefaultConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                PluginConfig = Config.ReadObject<Configuration>();
                if (PluginConfig == null)
                {
                    throw new JsonException();
                }

                if (MaybeUpdateConfig(PluginConfig))
                {
                    LogWarning("Configuration appears to be outdated; updating and saving");
                    SaveConfig();
                }
            }
            catch
            {
                LogWarning($"Configuration file {Name}.json is invalid; using defaults");
                LoadDefaultConfig();
            }
        }

        protected override void SaveConfig()
        {
            Log($"Configuration changes saved to {Name}.json");
            Config.WriteObject(PluginConfig, true);
        }

        #endregion
    }
}
