using System.Diagnostics;
using FFXIVHelpers.Models;
using Sharlayan.Core;

namespace FFXIVHelpers.Test;

public class ChatLogParserTest
{
    private static byte[] ExtractResource(string filename)
    {
        var assembly = System.Reflection.Assembly.GetExecutingAssembly();
        using var resFilestream = assembly.GetManifestResourceStream(filename);
        Debug.Assert(resFilestream != null, "File not found: " + filename);
        var byteArray = new byte[resFilestream.Length];
        var readByteCount = resFilestream.Read(byteArray, 0, byteArray.Length);
        Debug.Assert(readByteCount != 0, "File empty: " + filename);
        return byteArray;
    }

    private const string ChannelCode = "0010";
    private const string TellChatReceivingChannel = "000D";

    /// <summary>
    /// A specific set of byte streams generated by FFXIV can be converted to a human readable representation
    /// </summary>
    [Test]
    [TestCaseSource(nameof(GetChatsFromOtherCharacter))]
    public void UserReadableOutput(string fileName, byte[] actualInput, string expectedData)
    {
        // create logger
        var ffxivByteHandler = new FFXIVByteHandler(new NUnitLogger(), ChannelCode, "Sereth Milbana", "Cerberus");
        Assert.That(ffxivByteHandler.TryFFXIVToDiscordFriendly(new ChatLogItem(){Bytes = actualInput, Line = System.Text.Encoding.UTF8.GetString(actualInput), Code = ChannelCode}, out var result), Is.True);
        Debug.Assert(result != null, "Couldn't parse FFXIV message");
        CollectionAssert.AreEqual(expectedData, $"<{result.Character.Format(CharacterNameDisplay.WithWorld)}> {result.Message}");
    }
    
    /// <summary>
    /// Only messages from the expected channel are converted
    /// </summary>
    /// <remarks>
    /// Valid data is used, but only the channel code is changed
    /// </remarks>
    [Test]
    [TestCaseSource(nameof(GetChatsFromOtherCharacter))]
    public void IgnoreIrrelevantChannel(string fileName, byte[] actualInput, string expectedData)
    {
        var ffxivByteHandler = new FFXIVByteHandler(new NUnitLogger(), ChannelCode+"_", "Sereth Milbana", "Cerberus");
        Assert.That(ffxivByteHandler.TryFFXIVToDiscordFriendly(new ChatLogItem(){Bytes = actualInput, Line = System.Text.Encoding.UTF8.GetString(actualInput), Code = ChannelCode}, out var result), Is.False);
        Assert.That(result, Is.Null);
    }
    
    /// <summary>
    /// Handles Tell Messages no matter the expected channel
    /// </summary>
    [Test]
    [TestCaseSource(nameof(GetChatsFromOtherCharacter))]
    public void HandlesTellMessages(string fileName, byte[] actualInput, string expectedData)
    {
        var ffxivByteHandler = new FFXIVByteHandler(new NUnitLogger(), ChannelCode, "Sereth Milbana", "Cerberus");
        Assert.That(ffxivByteHandler.TryFFXIVToDiscordFriendly(new ChatLogItem(){Bytes = actualInput, Line = System.Text.Encoding.UTF8.GetString(actualInput), Code = TellChatReceivingChannel}, out var result), Is.True);
        Debug.Assert(result != null, "Couldn't parse FFXIV message");
        Assert.That(result, Is.TypeOf<FromTellMessage>());
    }
    
    /// <summary>
    /// The character used as bot is ignored
    /// </summary>
    [Test]
    [TestCaseSource(nameof(GetChatsFromOtherCharacter))]
    public void IgnoreMessagesFromCharacterThatActsAsBot(string fileName, byte[] actualInput, string expectedData)
    {
        var ffxivByteHandler = new FFXIVByteHandler(new NUnitLogger(), ChannelCode, "Sereth Milbana", "Cerberus");
        Assert.Multiple(() =>
        {
            Assert.That(ffxivByteHandler.TryFFXIVToDiscordFriendly(new ChatLogItem() { Bytes = actualInput, Line = "Sereth Milbana: " + System.Text.Encoding.UTF8.GetString(actualInput), Code = ChannelCode }, out var result), Is.False);
            Assert.That(result, Is.Null);
        });
    }

    /// <summary>
    /// The character used as bot is ignored, unless "FORCEEXEC" is part of the message
    /// </summary>
    [Test]
    [TestCaseSource(nameof(GetChatsFromOwnCharacter))]
    public void ForceMessagesFromCharacterThatActsAsBot(string fileName, byte[] actualInput, string expectedData)
    {
        var ffxivByteHandler = new FFXIVByteHandler(new NUnitLogger(), ChannelCode, "Sereth Milbana", "Cerberus");
        Assert.That(ffxivByteHandler.TryFFXIVToDiscordFriendly(new ChatLogItem(){Bytes = actualInput, Line = "Sereth Milbana: " + System.Text.Encoding.UTF8.GetString(actualInput) + "FORCEEXEC", Code = ChannelCode}, out var result), Is.True);
        Debug.Assert(result != null, "Couldn't parse FFXIV message");
        CollectionAssert.AreEqual(expectedData, $"<{result.Character.Format(CharacterNameDisplay.WithWorld)}> {result.Message}");
    }
    
    /// <summary>
    /// The character used as bot is ignored
    /// </summary>
    [Test]
    [TestCaseSource(nameof(GetInvalidChats))]
    public void HandleInvalidChats(string fileName, byte[] actualInput, string expectedData)
    {
        var ffxivByteHandler = new FFXIVByteHandler(new NUnitLogger(), ChannelCode, "Sereth Milbana", "Cerberus");
        Assert.That(ffxivByteHandler.TryFFXIVToDiscordFriendly(new ChatLogItem(){Bytes = actualInput, Line = System.Text.Encoding.UTF8.GetString(actualInput), Code = ChannelCode}, out var result), Is.False);
        Assert.That(result, Is.Null);
    }
    
    /// <summary>
    /// The character used as bot is ignored
    /// </summary>
    [Test]
    [TestCaseSource(nameof(GetEmptyChats))]
    public void HandleEmptyChats(string fileName, byte[] actualInput, string expectedData)
    {
        var ffxivByteHandler = new FFXIVByteHandler(new NUnitLogger(), ChannelCode, "Sereth Milbana", "Cerberus");
        Assert.That(ffxivByteHandler.TryFFXIVToDiscordFriendly(new ChatLogItem(){Bytes = actualInput, Line = System.Text.Encoding.UTF8.GetString(actualInput), Code = ChannelCode}, out var result), Is.False);
        Assert.That(result, Is.Null);
    }

    public static IEnumerable<object[]> GetChatsFromOtherCharacter()
    {
        var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
        return currentAssembly.GetManifestResourceNames()
            .Where(resourcePath => resourcePath.EndsWith(".binary"))
            .Where(resourcePath => resourcePath.Contains("FFXIVHelpers"))
            .Where(resourcePath => resourcePath.Contains("ChatsFromOtherCharacters"))
            .Select(pathToBinary => new object[] {
                pathToBinary.Replace(".binary", ""),
                ExtractResource(pathToBinary),
                System.Text.Encoding.UTF8.GetString(ExtractResource(pathToBinary.Replace(".binary", ".result")))
            });
    }

    public static IEnumerable<object[]> GetChatsFromOwnCharacter()
    {
        var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
        return currentAssembly.GetManifestResourceNames()
            .Where(resourcePath => resourcePath.EndsWith(".binary"))
            .Where(resourcePath => resourcePath.Contains("FFXIVHelpers"))
            .Where(resourcePath => resourcePath.Contains("ChatsFromOwnCharacter"))
            .Select(pathToBinary => new object[] {
                pathToBinary.Replace(".binary", ""),
                ExtractResource(pathToBinary),
                System.Text.Encoding.UTF8.GetString(ExtractResource(pathToBinary.Replace(".binary", ".result")))
            });
    }

    public static IEnumerable<object[]> GetInvalidChats()
    {
        var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
        return currentAssembly.GetManifestResourceNames()
            .Where(resourcePath => resourcePath.EndsWith(".binary"))
            .Where(resourcePath => resourcePath.Contains("FFXIVHelpers"))
            .Where(resourcePath => resourcePath.Contains("InvalidChats"))
            .Select(pathToBinary => new object[] {
                pathToBinary.Replace(".binary", ""),
                ExtractResource(pathToBinary),
                System.Text.Encoding.UTF8.GetString(ExtractResource(pathToBinary.Replace(".binary", ".result")))
            });
    }

    public static IEnumerable<object[]> GetEmptyChats()
    {
        var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
        return currentAssembly.GetManifestResourceNames()
            .Where(resourcePath => resourcePath.EndsWith(".binary"))
            .Where(resourcePath => resourcePath.Contains("FFXIVHelpers"))
            .Where(resourcePath => resourcePath.Contains("EmptyChats"))
            .Select(pathToBinary => new object[] {
                pathToBinary.Replace(".binary", ""),
                ExtractResource(pathToBinary),
                System.Text.Encoding.UTF8.GetString(ExtractResource(pathToBinary.Replace(".binary", ".result")))
            });
    }
}