using System.Text.Json;
using Google.Protobuf;
using ZB.MOM.WW.MxGateway.Contracts;
using ZB.MOM.WW.MxGateway.Contracts.Proto;

namespace ZB.MOM.WW.MxGateway.Tests.Contracts;

public sealed class ClientProtoInputTests
{
    /// <summary>Verifies that the proto inputs manifest declares current protocol versions and existing source files.</summary>
    [Fact]
    public void Manifest_DeclaresCurrentProtocolVersionsAndExistingInputs()
    {
        DirectoryInfo repositoryRoot = FindRepositoryRoot();
        string manifestPath = Path.Combine(repositoryRoot.FullName, "clients", "proto", "proto-inputs.json");

        using JsonDocument manifest = JsonDocument.Parse(File.ReadAllText(manifestPath));
        JsonElement root = manifest.RootElement;

        Assert.Equal(1, root.GetProperty("schemaVersion").GetInt32());
        Assert.Equal(GatewayContractInfo.GatewayProtocolVersion, root.GetProperty("gatewayProtocolVersion").GetUInt32());
        Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, root.GetProperty("workerProtocolVersion").GetUInt32());

        string protoRoot = Path.Combine(repositoryRoot.FullName, root.GetProperty("protoRoot").GetString()!);
        foreach (JsonElement sourceFile in root.GetProperty("sourceFiles").EnumerateArray())
        {
            string sourcePath = Path.Combine(protoRoot, sourceFile.GetProperty("path").GetString()!);
            Assert.True(File.Exists(sourcePath), $"Expected proto source file '{sourcePath}' to exist.");
        }

        foreach (JsonProperty output in root.GetProperty("generatedOutputs").EnumerateObject())
        {
            string outputPath = Path.Combine(repositoryRoot.FullName, output.Value.GetString()!);
            Assert.True(Directory.Exists(outputPath), $"Expected generated output directory '{outputPath}' to exist.");
        }
    }

    /// <summary>Verifies that the OpenSessionReply fixture parses with the current contract version.</summary>
    [Fact]
    public void OpenSessionReplyFixture_ParsesWithCurrentContract()
    {
        OpenSessionReply reply = ParseFixture(
            "open-session-reply.ok.json",
            OpenSessionReply.Parser);

        Assert.Equal(GatewayContractInfo.GatewayProtocolVersion, reply.GatewayProtocolVersion);
        Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, reply.WorkerProtocolVersion);
        Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
    }

    /// <summary>Verifies that the RegisterCommand fixture parses with the current contract version.</summary>
    [Fact]
    public void RegisterCommandRequestFixture_ParsesWithCurrentContract()
    {
        MxCommandRequest request = ParseFixture(
            "register-command-request.json",
            MxCommandRequest.Parser);

        Assert.Equal(MxCommandKind.Register, request.Command.Kind);
        Assert.Equal("fixture-client", request.Command.Register.ClientName);
    }

    /// <summary>Verifies that the OnDataChange event fixture parses with the current contract version.</summary>
    [Fact]
    public void OnDataChangeEventFixture_ParsesWithCurrentContract()
    {
        MxEvent gatewayEvent = ParseFixture(
            "on-data-change-event.json",
            MxEvent.Parser);

        Assert.Equal(MxEventFamily.OnDataChange, gatewayEvent.Family);
        Assert.Equal(1ul, gatewayEvent.WorkerSequence);
        Assert.Equal(MxDataType.Integer, gatewayEvent.Value.DataType);
        Assert.Equal(MxEvent.BodyOneofCase.OnDataChange, gatewayEvent.BodyCase);
    }

    private static T ParseFixture<T>(
        string fixtureName,
        MessageParser<T> parser)
        where T : IMessage<T>
    {
        DirectoryInfo repositoryRoot = FindRepositoryRoot();
        string fixturePath = Path.Combine(repositoryRoot.FullName, "clients", "proto", "fixtures", "golden", fixtureName);

        return parser.ParseJson(File.ReadAllText(fixturePath));
    }

    private static DirectoryInfo FindRepositoryRoot()
    {
        DirectoryInfo? current = new(AppContext.BaseDirectory);

        while (current is not null)
        {
            if (File.Exists(Path.Combine(current.FullName, "CLAUDE.md"))
                && Directory.Exists(Path.Combine(current.FullName, "src"))
                && Directory.Exists(Path.Combine(current.FullName, "clients")))
            {
                return current;
            }

            current = current.Parent;
        }

        throw new DirectoryNotFoundException("Could not locate the repository root from the test output directory.");
    }
}
