using System.Reflection;
using Autofac;

namespace e_suite.DependencyInjection;

public class ModuleDiscovery : IModuleDiscovery
{
    private readonly IFileSystem _fileSystem;
    private readonly IAssemblyLoader _assemblyLoader;
    private readonly IAssemblyIdentity _assemblyIdentity;

    public ModuleDiscovery(IFileSystem fileSystem, IAssemblyLoader assemblyLoader, IAssemblyIdentity assemblyIdentity)
    {
        _fileSystem = fileSystem;
        _assemblyLoader = assemblyLoader;
        _assemblyIdentity = assemblyIdentity;
    }

    public void RegisterAllModuleIocRegistrations(ContainerBuilder builder)
    {
        var cacheDirectory = Path.Combine(AppContext.BaseDirectory, ".cache");
        _fileSystem.CreateDirectory(cacheDirectory);

        var cachePath = Path.Combine(cacheDirectory, "module-cache.json");

        var cache = LoadOrCreateModuleCache(cachePath);

        var basePath = AppContext.BaseDirectory;
        var assemblies = new List<Assembly>();

        foreach (var module in cache.Modules)
        {
            var fullPath = Path.Combine(basePath, module);

            if (!_fileSystem.FileExists(fullPath))
                throw new FileNotFoundException($"Cached module not found: {fullPath}");

            assemblies.Add(_assemblyLoader.LoadFrom(fullPath));
        }

        var registrationTypes = assemblies
            .SelectMany(a => a.GetTypes())
            .Where(t => typeof(IIocRegistration).IsAssignableFrom(t)
                        && t is { IsAbstract: false, IsInterface: false })
            .ToList();

        foreach (var type in registrationTypes)
        {
            var instance = (IIocRegistration)Activator.CreateInstance(type)!;
            instance.RegisterTypes(builder);
        }
    }

    private ModuleCache LoadOrCreateModuleCache(string cachePath)
    {
        var currentHash = ComputeAssemblyHash();

        if (_fileSystem.FileExists(cachePath))
        {
            var json = _fileSystem.ReadAllText(cachePath);
            var cache = System.Text.Json.JsonSerializer.Deserialize<ModuleCache>(json);

            if (cache is not null && cache.AssemblyHash == currentHash)
                return cache;
        }

        // Cache missing or invalid → regenerate
        var regenerated = RegenerateModuleCache(currentHash);

        var output = System.Text.Json.JsonSerializer.Serialize(regenerated, new System.Text.Json.JsonSerializerOptions
        {
            WriteIndented = true
        });

        _fileSystem.WriteAllText(cachePath, output);

        return regenerated;
    }

    private  ModuleCache RegenerateModuleCache(string assemblyHash)
    {
        var basePath = AppContext.BaseDirectory;
        var dlls = _fileSystem.GetFiles(basePath, "*.dll");

        var modules = new List<string>();

        foreach (var dll in dlls)
        {
            try
            {
                var asm = _assemblyLoader.LoadFrom(dll);

                var hasRegistration = asm.GetTypes().Any(t =>
                    typeof(IIocRegistration).IsAssignableFrom(t) &&
                    !t.IsAbstract &&
                    !t.IsInterface);

                if (hasRegistration)
                    modules.Add(Path.GetFileName(dll));
            }
            catch
            {
                // Ignore non-.NET DLLs or load failures
            }
        }

        return new ModuleCache
        {
            AssemblyHash = assemblyHash,
            Modules = modules
        };
    }

    private string ComputeAssemblyHash()
    {
        var mainAssembly = _assemblyIdentity.GetAssemblyForHashing();
        var path = mainAssembly.Location;

        using var stream = _fileSystem.OpenRead(path);
        using var sha = System.Security.Cryptography.SHA256.Create();

        var hash = sha.ComputeHash(stream);
        return Convert.ToHexString(hash);
    }
}