using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

namespace ZB.MOM.WW.MxGateway.Worker.Tests.ProjectStructure;

public sealed class WorkerProjectReferenceTests
{
    /// <summary>Verifies that the worker project targets .NET Framework 4.8 with x86 platform.</summary>
    [Fact]
    public void WorkerProject_TargetsNet48AndX86()
    {
        XDocument project = LoadProject("ZB.MOM.WW.MxGateway.Worker");

        Assert.Equal("net48", ElementValue(project, "TargetFramework"));
        Assert.Equal("x86", ElementValue(project, "PlatformTarget"));
        Assert.Equal("true", ElementValue(project, "Prefer32Bit"));
    }

    /// <summary>Verifies that the worker test project targets .NET Framework 4.8 with x86 platform.</summary>
    [Fact]
    public void WorkerTestProject_TargetsNet48AndX86()
    {
        XDocument project = LoadProject("ZB.MOM.WW.MxGateway.Worker.Tests");

        Assert.Equal("net48", ElementValue(project, "TargetFramework"));
        Assert.Equal("x86", ElementValue(project, "PlatformTarget"));
    }

    /// <summary>
    ///     Verifies that the MXAccess COM interop is referenced only by the
    ///     worker project and its test project — never by the gateway server
    ///     or the contracts project. The gateway must never load MXAccess COM
    ///     directly (see <c>gateway.md</c>); the worker test project
    ///     legitimately references the interop so it can exercise the
    ///     COM-facing worker code (e.g. <c>WnWrapAlarmConsumer</c>).
    /// </summary>
    [Fact]
    public void MxAccessInteropReference_ExistsOnlyInWorkerAndWorkerTestProjects()
    {
        DirectoryInfo repositoryRoot = FindRepositoryRoot();
        string[] projectFiles = Directory.GetFiles(repositoryRoot.FullName, "*.csproj", SearchOption.AllDirectories)
            .Where(path => path.IndexOf($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) < 0)
            .Where(path => path.IndexOf($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) < 0)
            .ToArray();

        IReadOnlyList<string> projectsWithMxAccessReference = projectFiles
            .Where(ProjectReferencesMxAccess)
            .Select(path => Path.GetFileNameWithoutExtension(path))
            .OrderBy(name => name, StringComparer.Ordinal)
            .ToArray();

        Assert.Equal(
            ["ZB.MOM.WW.MxGateway.Worker", "ZB.MOM.WW.MxGateway.Worker.Tests"],
            projectsWithMxAccessReference);
    }

    private static bool ProjectReferencesMxAccess(string projectPath)
    {
        XDocument project = XDocument.Load(projectPath);

        return project
            .Descendants()
            .Where(element => element.Name.LocalName is "Reference" or "COMReference" or "COMFileReference" or "PackageReference")
            .Select(element => (string?)element.Attribute("Include") ?? string.Empty)
            .Concat(project.Descendants().Where(element => element.Name.LocalName == "HintPath").Select(element => element.Value))
            .Any(reference =>
                reference.IndexOf("MxAccess", StringComparison.OrdinalIgnoreCase) >= 0
                || reference.IndexOf("ArchestrA.MXAccess", StringComparison.OrdinalIgnoreCase) >= 0
                || reference.IndexOf("LMXProxy", StringComparison.OrdinalIgnoreCase) >= 0);
    }

    private static XDocument LoadProject(string projectName)
    {
        DirectoryInfo repositoryRoot = FindRepositoryRoot();
        string projectPath = Path.Combine(repositoryRoot.FullName, projectName, $"{projectName}.csproj");

        return XDocument.Load(projectPath);
    }

    private static string ElementValue(XDocument project, string elementName)
    {
        return project
            .Descendants()
            .Single(element => element.Name.LocalName == elementName)
            .Value;
    }

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

        while (current is not null)
        {
            if (File.Exists(Path.Combine(current.FullName, "ZB.MOM.WW.MxGateway.slnx")))
            {
                return current;
            }

            current = current.Parent;
        }

        throw new DirectoryNotFoundException("Could not locate src/ZB.MOM.WW.MxGateway.slnx from the test output directory.");
    }
}
