using System;
using System.IO;
using System.Reflection;
using Xunit;

namespace TeslaConsole.DiffTests
{
    /// <summary>
    /// Shared per-test-class fixture that spins up one child AppDomain per assembly
    /// (original + recovered), each rooted at that assembly's own directory so the CLR
    /// probes for any dependencies next to it. The Invoker proxy in each domain loads
    /// and exercises that assembly.
    /// </summary>
    public sealed class DifferentialFixture : IDisposable
    {
        private readonly AppDomain _originalDomain;
        private readonly AppDomain _recoveredDomain;

        public Invoker Original { get; }
        public Invoker Recovered { get; }

        public DifferentialFixture()
        {
            Assert.True(File.Exists(AssemblyPaths.OriginalExe),
                "Original baseline not found: " + AssemblyPaths.OriginalExe);
            Assert.True(File.Exists(AssemblyPaths.RecoveredExe),
                "Recovered build not found (build TeslaConsole.csproj -c Release first): " +
                AssemblyPaths.RecoveredExe);

            _originalDomain = CreateDomain("Original", AssemblyPaths.OriginalExe);
            _recoveredDomain = CreateDomain("Recovered", AssemblyPaths.RecoveredExe);

            Original = CreateInvoker(_originalDomain, AssemblyPaths.OriginalExe);
            Recovered = CreateInvoker(_recoveredDomain, AssemblyPaths.RecoveredExe);
        }

        private static AppDomain CreateDomain(string name, string exePath)
        {
            var setup = new AppDomainSetup
            {
                ApplicationBase = Path.GetDirectoryName(exePath),
            };
            return AppDomain.CreateDomain("TeslaDiff_" + name, null, setup);
        }

        private static Invoker CreateInvoker(AppDomain domain, string exePath)
        {
            // Probe directory = recovered output, which ships every dependency DLL.
            string probeDir = Path.GetDirectoryName(AssemblyPaths.RecoveredExe);
            return (Invoker)domain.CreateInstanceFromAndUnwrap(
                typeof(Invoker).Assembly.Location,
                typeof(Invoker).FullName,
                false,
                BindingFlags.Default,
                null,
                new object[] { exePath, probeDir },
                null,
                null);
        }

        /// <summary>Run a case in both domains and return (original, recovered).</summary>
        public (string Original, string Recovered) RunBoth(string caseName, params string[] args)
        {
            return (Original.Run(caseName, args), Recovered.Run(caseName, args));
        }

        public void Dispose()
        {
            if (_originalDomain != null) AppDomain.Unload(_originalDomain);
            if (_recoveredDomain != null) AppDomain.Unload(_recoveredDomain);
        }
    }
}
