using System;
using System.Linq;
using Brigadier.NET;
using Brigadier.NET.Builder;
using MinecraftClient.CommandHandler;
using MinecraftClient.CommandHandler.Patch;
using MinecraftClient.Mapping;
using MinecraftClient.Scripting;
using Tomlet.Attributes;

namespace MinecraftClient.ChatBots
{
    public class FollowPlayer : ChatBot
    {
        public const string CommandName = "follow";

        public static Configs Config = new();

        [TomlDoNotInlineObject]
        public class Configs
        {
            [NonSerialized]
            private const string BotName = "FollowPlayer";

            public bool Enabled = false;

            [TomlInlineComment("$ChatBot.FollowPlayer.Update_Limit$")]
            public double Update_Limit = 1.5;

            [TomlInlineComment("$ChatBot.FollowPlayer.Stop_At_Distance$")]
            public double Stop_At_Distance = 3.0;

            public void OnSettingUpdate()
            {
                if (Update_Limit < 0)
                    Update_Limit = 0;

                if (Stop_At_Distance < 0)
                    Stop_At_Distance = 0;
            }
        }

        private string? _playerToFollow = null;
        private int _updateCounter = 0;
        private bool _unsafeEnabled = false;

        public override void Initialize()
        {
            if (!GetEntityHandlingEnabled())
            {
                LogToConsole(Translations.extra_entity_required);
                LogToConsole(Translations.general_bot_unload);
                UnloadBot();
                return;
            }

            if (!GetTerrainEnabled())
            {
                LogToConsole(Translations.extra_terrainandmovement_required);
                LogToConsole(Translations.general_bot_unload);
                UnloadBot();
                return;
            }

            McClient.dispatcher.Register(l => l.Literal("help")
                .Then(l => l.Literal(CommandName)
                    .Executes(r => OnCommandHelp(r.Source, string.Empty))
                )
            );

            McClient.dispatcher.Register(l => l.Literal(CommandName)
                .Then(l => l.Literal("start")
                    .Then(l => l.Argument("PlayerName", MccArguments.PlayerName())
                        .Executes(r => OnCommandStart(r.Source, Arguments.GetString(r, "PlayerName"), takeRisk: false))
                        .Then(l => l.Literal("-f")
                            .Executes(r => OnCommandStart(r.Source, Arguments.GetString(r, "PlayerName"), takeRisk: true)))))
                .Then(l => l.Literal("stop")
                    .Executes(r => OnCommandStop(r.Source)))
                .Then(l => l.Literal("_help")
                    .Executes(r => OnCommandHelp(r.Source, string.Empty))
                    .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName)))
            );
        }

        public override void OnUnload()
        {
            McClient.dispatcher.Unregister(CommandName);
            McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName);
        }

        private int OnCommandHelp(CmdResult r, string? cmd)
        {
            return r.SetAndReturn(cmd switch
            {
#pragma warning disable format // @formatter:off
                _           =>   Translations.cmd_follow_desc + ": " + Translations.cmd_follow_usage
                                   + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false),
#pragma warning restore format // @formatter:on
            });
        }

        private int OnCommandStart(CmdResult r, string name, bool takeRisk)
        {
            if (!IsValidName(name))
                return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_invalid_name);

            Entity? player = GetEntities().Values.ToList().Find(entity =>
                entity.Type == EntityType.Player
                && !string.IsNullOrEmpty(entity.Name)
                && entity.Name.Equals(name, StringComparison.OrdinalIgnoreCase));

            if (player == null)
                return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_invalid_player);

            if (!CanMoveThere(player.Location))
                return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_cant_reach_player);

            if (_playerToFollow != null && _playerToFollow.Equals(name, StringComparison.OrdinalIgnoreCase))
                return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_follow_already_following, _playerToFollow));

            string result;
            if (_playerToFollow != null)
                result = string.Format(Translations.cmd_follow_switched, player.Name!);
            else
                result = string.Format(Translations.cmd_follow_started, player.Name!);
            _playerToFollow = name.ToLower();

            if (takeRisk)
            {
                _unsafeEnabled = true;
                return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_follow_note + '\n' + Translations.cmd_follow_unsafe_enabled);
            }
            else
                return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_follow_note);
        }

        private int OnCommandStop(CmdResult r)
        {
            if (_playerToFollow == null)
                return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_already_stopped);

            _playerToFollow = null;

            return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_follow_stopping);
        }

        public override void Update()
        {
            _updateCounter++;
        }

        public override void OnEntityMove(Entity entity)
        {
            if (entity.Type != EntityType.Player)
                return;

            if (_playerToFollow == null || string.IsNullOrEmpty(entity.Name))
                return;

            if (_playerToFollow != entity.Name.ToLower())
                return;

            if (_updateCounter < Settings.DoubleToTick(Config.Update_Limit))
                return;

            _updateCounter = 0;

            if (!CanMoveThere(entity.Location))
                return;

            // Stop at specified distance from plater (prevents pushing player around)
            double distance = entity.Location.Distance(GetCurrentLocation());

            if (distance < Config.Stop_At_Distance)
                return;

            MoveToLocation(entity.Location, _unsafeEnabled);
        }

        public override void OnEntitySpawn(Entity entity)
        {
            if (entity.Type != EntityType.Player)
                return;

            if (_playerToFollow != null && !string.IsNullOrEmpty(entity.Name) && _playerToFollow.Equals(entity.Name, StringComparison.OrdinalIgnoreCase))
            {
                LogToConsole(string.Format(Translations.cmd_follow_player_came_to_the_range, _playerToFollow));
                LogToConsole(Translations.cmd_follow_resuming);
            }
        }

        public override void OnEntityDespawn(Entity entity)
        {
            if (entity.Type != EntityType.Player)
                return;

            if (_playerToFollow != null && !string.IsNullOrEmpty(entity.Name) && _playerToFollow.Equals(entity.Name, StringComparison.OrdinalIgnoreCase))
            {
                LogToConsole(string.Format(Translations.cmd_follow_player_left_the_range, _playerToFollow));
                LogToConsole(Translations.cmd_follow_pausing);
            }
        }

        public override void OnPlayerLeave(Guid uuid, string? name)
        {
            if (_playerToFollow != null && !string.IsNullOrEmpty(name) && _playerToFollow.Equals(name, StringComparison.OrdinalIgnoreCase))
            {
                LogToConsole(string.Format(Translations.cmd_follow_player_left, _playerToFollow));
                LogToConsole(Translations.cmd_follow_stopping);
                _playerToFollow = null;
            }
        }

        private bool CanMoveThere(Location location)
        {
            ChunkColumn? chunkColumn = GetWorld().GetChunkColumn(location);

            if (chunkColumn == null || chunkColumn.FullyLoaded == false)
                return false;

            return true;
        }
    }
}