using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

namespace StellaOps.Plugin.Hosting;

internal sealed class PluginLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver resolver;
    private readonly IEnumerable<Assembly> hostAssemblies;

    public PluginLoadContext(string pluginPath)
        : base(isCollectible: false)
    {
        resolver = new AssemblyDependencyResolver(pluginPath);
        hostAssemblies = AssemblyLoadContext.Default.Assemblies;
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        // Attempt to reuse assemblies that already exist in the default context when versions are compatible.
        var existing = hostAssemblies.FirstOrDefault(a => string.Equals(
            a.GetName().Name,
            assemblyName.Name,
            StringComparison.OrdinalIgnoreCase));

        if (existing != null && IsCompatible(existing.GetName(), assemblyName))
        {
            return existing;
        }

        var assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
        if (!string.IsNullOrEmpty(assemblyPath))
        {
            return LoadFromAssemblyPath(assemblyPath);
        }

        return null;
    }

    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
    {
        var libraryPath = resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
        if (!string.IsNullOrEmpty(libraryPath))
        {
            return LoadUnmanagedDllFromPath(libraryPath);
        }

        return IntPtr.Zero;
    }

    private static bool IsCompatible(AssemblyName hostAssembly, AssemblyName pluginAssembly)
    {
        if (hostAssembly.Version == pluginAssembly.Version)
        {
            return true;
        }

        if (hostAssembly.Version is null || pluginAssembly.Version is null)
        {
            return false;
        }

        if (hostAssembly.Version.Major == pluginAssembly.Version.Major &&
            hostAssembly.Version.Minor >= pluginAssembly.Version.Minor)
        {
            return true;
        }

        if (hostAssembly.Version.Major >= pluginAssembly.Version.Major)
        {
            return true;
        }

        return false;
    }
}