﻿using Network;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using System.ComponentModel;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Oxide.Plugins;

[Info("Magic Hostile Panel", "MJSU", "1.0.11")]
[Description("Displays how much longer a player is considered hostile")]
public class MagicHostilePanel : RustPlugin
{
    #region Class Fields

    [PluginReference] private readonly Plugin MagicPanel;

    private PluginConfig _pluginConfig;
    private string _panelText;
    private UpdateEnum _updateType;

    private readonly Hash<ulong, Timer> _hostileTimer = new();

    private enum UpdateEnum
    {
        None = 0,
        All = 1,
        Panel = 2,
        Image = 3,
        Text = 4
    }

    #endregion

    #region Setup & Loading

    private void Init()
    {
        Unsubscribe(nameof(OnEntityMarkHostile));
        Unsubscribe(nameof(OnPlayerConnected));
        _panelText = _pluginConfig.Panel.Text.Text;
    }

    protected override void LoadDefaultConfig()
    {
        PrintWarning("Loading Default Config");
    }

    protected override void LoadConfig()
    {
        var path = $"{Manager.ConfigPath}/MagicPanel/{Name}.json";
        var newConfig = new DynamicConfigFile(path);
        if (!newConfig.Exists())
        {
            LoadDefaultConfig();
            newConfig.Save();
        }

        try
        {
            newConfig.Load();
        }
        catch (Exception ex)
        {
            RaiseError($"Failed to load config file {path}: {ex.Message}");
            return;
        }

        newConfig.Settings.DefaultValueHandling = DefaultValueHandling.Populate;
        _pluginConfig = AdditionalConfig(newConfig.ReadObject<PluginConfig>());
        newConfig.WriteObject(_pluginConfig);
    }

    private PluginConfig AdditionalConfig(PluginConfig config)
    {
        config.Panel = new Panel
        {
            Image = new PanelImage
            {
                Enabled = config.Panel?.Image?.Enabled ?? true,
                Color = config.Panel?.Image?.Color ?? "#FFFFFFFF",
                Order = config.Panel?.Image?.Order ?? 0,
                Width = config.Panel?.Image?.Width ?? 0.3f,
                Url = config.Panel?.Image?.Url ??
                      "https://i.postimg.cc/JzWfmntW/v5sdNHg.png",
                Padding = config.Panel?.Image?.Padding ??
                          new TypePadding(0.05f, 0.05f, 0.15f, 0.15f)
            },
            Text = new PanelText
            {
                Enabled = config.Panel?.Text?.Enabled ?? true,
                Color = config.Panel?.Text?.Color ?? "#FFFFFFFF",
                Order = config.Panel?.Text?.Order ?? 1,
                Width = config.Panel?.Text?.Width ?? 0.7f,
                FontSize = config.Panel?.Text?.FontSize ?? 14,
                Padding = config.Panel?.Text?.Padding ??
                          new TypePadding(0.05f, 0.05f, 0.05f, 0.05f),
                TextAnchor = config.Panel?.Text?.TextAnchor ?? TextAnchor.MiddleCenter,
                Text = config.Panel?.Text?.Text ?? "{0}m {1:00}s"
            }
        };
        config.PanelSettings = new PanelRegistration
        {
            BackgroundColor = config.PanelSettings?.BackgroundColor ?? "#fff2df08",
            Dock = config.PanelSettings?.Dock ?? "centerupper",
            Order = config.PanelSettings?.Order ?? 14,
            Width = config.PanelSettings?.Width ?? 0.0725f
        };
        return config;
    }

    private void OnServerInitialized()
    {
        MagicPanelRegisterPanels();
    }

    private void Unload()
    {
        foreach (var t in _hostileTimer.Values)
        {
            t?.Destroy();
        }

        _hostileTimer.Clear();
    }

    #endregion

    #region uMod Hooks

    private void OnEntityMarkHostile(BasePlayer player) =>
        NextTick(() => SetupHostile(player));

    private void OnPlayerConnected(BasePlayer player)
    {
        if (!player) return;

        if (player.IsReceivingSnapshot)
        {
            // wait for player to enter the game
            timer.In(1f, () => OnPlayerConnected(player));
            return;
        }

        NextTick(() => SetupHostile(player));
    }

    #endregion

    #region MagicPanel Hooks

    private void MagicPanelRegisterPanels()
    {
        if (MagicPanel?.IsLoaded is not true)
        {
            PrintError("Missing plugin dependency MagicPanel: https://umod.org/plugins/magic-panel");
            return;
        }

        var updateImage =
            _pluginConfig.Panel.Image.Enabled && _pluginConfig.ChangeIconColor;
        _updateType = (updateImage, _pluginConfig.Panel.Text.Enabled) switch
        {
            (false, false) => UpdateEnum.None,
            (false, true) => UpdateEnum.Text,
            (true, false) => UpdateEnum.Image,
            (true, true) => UpdateEnum.All
        };
        if (UpdateEnum.None == _updateType)
        {
            PrintWarning("Not registering panel because all items are disabled in config");
            return;
        }

        MagicPanel?.Call("RegisterPlayerPanel",
            this, Name, JsonConvert.SerializeObject(_pluginConfig.PanelSettings),
            nameof(GetPanel));
        timer.In(1f, () =>
        {
            foreach (var player in BasePlayer.activePlayerList)
            {
                SetupHostile(player);
            }

            Subscribe(nameof(OnEntityMarkHostile));
            Subscribe(nameof(OnPlayerConnected));
        });
    }

    private Hash<string, object> GetPanel(BasePlayer player)
    {
        var isHostile = player.IsHostile();
        var color =
            isHostile ? _pluginConfig.ActiveColor : _pluginConfig.InactiveColor;
        var panel = _pluginConfig.Panel;
        var text = panel.Text;
        if (text?.Enabled is true)
        {
            text.Color = color;

            var remainingTime = isHostile ? TimeSpan.FromSeconds(Mathf.CeilToInt(player.GetHostileDuration())) : TimeSpan.Zero;
            var minutes = remainingTime.Minutes;
            var seconds = remainingTime.Seconds;

            text.Text = string.Format(_panelText, minutes, seconds);
        }

        var image = panel.Image;
        if (image?.Enabled is true && _pluginConfig.ChangeIconColor)
        {
            image.Color = color;
        }

        return panel.ToHash();
    }

    #endregion

    #region Helper Methods

    private void SetupHostile(BasePlayer player)
    {
        if (player?.userID.IsSteamId() is not true)
        {
            return;
        }

        if (!player.IsHostile())
        {
            HidePanel(player);
            return;
        }

        // abort if timer already active
        if (_hostileTimer.ContainsKey(player.userID))
        {
            return;
        }

        ShowPanel(player);
        UpdatePanel(player);

        // update panel at configured rate as long as player is hostile
        _hostileTimer[player.userID] =
            timer.Every(_pluginConfig.UpdateRate, () =>
            {
                UpdatePanel(player);
                if (player.IsHostile()) return;
                // player is no longer hostile; stop tracking a timer and hide panel
                if (_hostileTimer.Remove(player.userID, out var t))
                {
                    t?.Destroy();
                }

                HidePanel(player);
            });
    }

    private void HidePanel(BasePlayer player)
    {
        if (_pluginConfig.ShowHide)
        {
            MagicPanel?.Call("HidePanel", player, Name);
        }
    }

    private void ShowPanel(BasePlayer player)
    {
        if (_pluginConfig.ShowHide)
        {
            MagicPanel?.Call("ShowPanel", player, Name);
        }
    }

    private void UpdatePanel(BasePlayer player) =>
        MagicPanel?.Call("UpdatePanel", player, Name, (int)_updateType);

    #endregion

    #region Classes

    private sealed class PluginConfig
    {
        [DefaultValue(false)]
        [JsonProperty(PropertyName = "Show/Hide panel")]
        public bool ShowHide { get; set; }

        [DefaultValue(false)]
        [JsonProperty(PropertyName = "Change Icon Color")]
        public bool ChangeIconColor { get; set; }

        [DefaultValue("#4EE44EFF")]
        [JsonProperty(PropertyName = "Active Color")]
        public string ActiveColor { get; set; }

        [DefaultValue("#B33333FF")]
        [JsonProperty(PropertyName = "Inactive Color")]
        public string InactiveColor { get; set; }

        [DefaultValue(1.0f)]
        [JsonProperty(PropertyName = "Update Rate (Seconds)")]
        public float UpdateRate { get; set; }

        [JsonProperty(PropertyName = "Panel Settings")]
        public PanelRegistration PanelSettings { get; set; }

        [JsonProperty(PropertyName = "Panel Layout")]
        public Panel Panel { get; set; }
    }

    private sealed class PanelRegistration
    {
        public string Dock { get; set; }
        public float Width { get; set; }
        public int Order { get; set; }
        public string BackgroundColor { get; set; }
    }

    private sealed class Panel
    {
        public PanelImage Image { get; set; }
        public PanelText Text { get; set; }

        [JsonIgnore] private Hash<string, object> _hash;

        public Hash<string, object> ToHash()
        {
            // only create new hash if none exists yet
            _hash ??= new Hash<string, object>();
            _hash[nameof(Image)] = Image.ToHash();
            _hash[nameof(Text)] = Text.ToHash();
            return _hash;
        }
    }

    private abstract class PanelType
    {
        public bool Enabled { get; set; }
        public string Color { get; set; }
        public int Order { get; set; }
        public float Width { get; set; }
        public TypePadding Padding { get; set; }

        [JsonIgnore] private Hash<string, object> _hash;

        public virtual Hash<string, object> ToHash()
        {
            // only create new hash if one doesn't yet exist
            // populate it with non-dynamic data only this once
            _hash ??= new Hash<string, object>
            {
                [nameof(Enabled)] = Enabled,
                [nameof(Order)] = Order,
                [nameof(Width)] = Width,
                [nameof(Padding)] = Padding.ToHash()
            };
            // update dynamic data every time
            _hash[nameof(Color)] = Color;
            return _hash;
        }
    }

    private sealed class PanelImage : PanelType
    {
        public string Url { get; set; }

        public override Hash<string, object> ToHash()
        {
            var hash = base.ToHash();
            // populate with non-dynamic data only when missing
            hash.TryAdd(nameof(Url), Url);
            return hash;
        }
    }

    private sealed class PanelText : PanelType
    {
        public string Text { get; set; }
        public int FontSize { get; set; }

        [JsonConverter(typeof(StringEnumConverter))]
        public TextAnchor TextAnchor { get; set; }

        public override Hash<string, object> ToHash()
        {
            var hash = base.ToHash();
            // update dynamic data every time
            hash[nameof(Text)] = Text;
            // populate with non-dynamic data only when missing
            hash.TryAdd(nameof(FontSize), FontSize);
            hash.TryAdd(nameof(TextAnchor), TextAnchor);
            return hash;
        }
    }

    private sealed class TypePadding
    {
        public float Left { get; set; }
        public float Right { get; set; }
        public float Top { get; set; }
        public float Bottom { get; set; }

        public TypePadding(float left, float right, float top, float bottom)
        {
            Left = left;
            Right = right;
            Top = top;
            Bottom = bottom;
        }

        [JsonIgnore] private Hash<string, object> _hash;

        public Hash<string, object> ToHash()
        {
            // only create new hash if one doesn't yet exist
            // populate it with non-changing data only on creation
            _hash ??= new Hash<string, object>
            {
                [nameof(Left)] = Left,
                [nameof(Right)] = Right,
                [nameof(Top)] = Top,
                [nameof(Bottom)] = Bottom
            };
            return _hash;
        }
    }

    #endregion
}