using NUnit.Framework;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Discord.Commands;
using EllieBot.Common;
using EllieBot.Common.Attributes;
using EllieBot.Services;

namespace EllieBot.Tests
{
    public class CommandStringsTests
    {
        private const string responsesPath = "../../../../EllieBot/strings/responses";
        private const string commandsPath = "../../../../EllieBot/strings/commands";
        private const string aliasesPath = "../../../../EllieBot/aliases.yml";

        [Test]
        public void AllCommandNamesHaveStrings()
        {
            var stringsSource = new LocalFileStringsSource(
                responsesPath,
                commandsPath);
            var strings = new MemoryBotStringsProvider(stringsSource);

            var culture = new CultureInfo("en-US");

            var isSuccess = true;
            foreach (var (methodName, _) in CommandNameLoadHelper.LoadAliases(aliasesPath))
            {
                var cmdStrings = strings.GetCommandStrings(culture.Name, methodName);
                if (cmdStrings is null)
                {
                    isSuccess = false;
                    TestContext.Out.WriteLine($"{methodName} doesn't exist in commands.en-US.yml");
                }
            }

            Assert.That(isSuccess, Is.True);
        }

        private static string[] GetCommandMethodNames()
            => typeof(Bot).Assembly
                .GetExportedTypes()
                .Where(type => type.IsClass && !type.IsAbstract)
                .Where(type => typeof(EllieModule).IsAssignableFrom(type) // if its a top level module
                               || !(type.GetCustomAttribute<GroupAttribute>(true) is null)) // or a submodule
                .SelectMany(x => x.GetMethods()
                    .Where(mi => mi.CustomAttributes
                        .Any(ca => ca.AttributeType == typeof(CmdAttribute))))
                .Select(x => x.Name.ToLowerInvariant())
                .ToArray();

        [Test]
        public void AllCommandMethodsHaveNames()
        {
            var allAliases = CommandNameLoadHelper.LoadAliases(
                aliasesPath);

            var methodNames = GetCommandMethodNames();

            var isSuccess = true;
            foreach (var methodName in methodNames)
            {
                if (!allAliases.TryGetValue(methodName, out _))
                {
                    TestContext.Error.WriteLine($"{methodName} is missing an alias.");
                    isSuccess = false;
                }
            }

            Assert.That(isSuccess, Is.True);
        }

        [Test]
        public void NoObsoleteAliases()
        {
            var allAliases = CommandNameLoadHelper.LoadAliases(aliasesPath);

            var methodNames = GetCommandMethodNames()
                .ToHashSet();

            var isSuccess = true;

            foreach (var item in allAliases)
            {
                var methodName = item.Key;

                if (!methodNames.Contains(methodName))
                {
                    TestContext.WriteLine($"'{methodName}' from aliases.yml doesn't have a matching command method.");
                    isSuccess = false;
                }
            }

            if (isSuccess)
                Assert.Pass();
            else
                Assert.Warn("There are some unused entries in aliases.yml");
        }

        [Test]
        public void NoObsoleteCommandStrings()
        {
            var stringsSource = new LocalFileStringsSource(responsesPath, commandsPath);

            var culture = new CultureInfo("en-US");

            var methodNames = GetCommandMethodNames()
                .ToHashSet();

            var isSuccess = true;
            // var allCommandNames = CommandNameLoadHelper.LoadCommandStrings(commandsPath));
            foreach (var entry in stringsSource.GetCommandStrings()[culture.Name])
            {
                var cmdName = entry.Key;

                if (!methodNames.Contains(cmdName))
                {
                    TestContext.Out.WriteLine(
                        $"'{cmdName}' from commands.en-US.yml doesn't have a matching command method.");
                    isSuccess = false;
                }
            }

            Assert.That(isSuccess, Is.True, "There are some unused command strings in strings/commands.en-US.yml");
        }
    }
}