using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Configuration;
using NetEscapades.Configuration.Yaml;
using StellaOps.Authority.Plugins.Abstractions;

namespace StellaOps.Configuration;

/// <summary>
/// Utility helpers for loading Authority plugin configuration manifests.
/// </summary>
public static class AuthorityPluginConfigurationLoader
{
    /// <summary>
    /// Loads plugin configuration files based on the supplied Authority options.
    /// </summary>
    /// <param name="options">Authority configuration containing plugin descriptors.</param>
    /// <param name="basePath">Application base path used to resolve relative directories.</param>
    /// <param name="configureBuilder">Optional hook to customise per-plugin configuration builder.</param>
    public static IReadOnlyList<AuthorityPluginContext> Load(
        StellaOpsAuthorityOptions options,
        string basePath,
        Action<IConfigurationBuilder>? configureBuilder = null)
    {
        ArgumentNullException.ThrowIfNull(options);
        ArgumentNullException.ThrowIfNull(basePath);

        var descriptorPairs = options.Plugins.Descriptors
            .OrderBy(static pair => pair.Key, StringComparer.OrdinalIgnoreCase)
            .ToArray();

        if (descriptorPairs.Length == 0)
        {
            return Array.Empty<AuthorityPluginContext>();
        }

        var configurationDirectory = ResolveConfigurationDirectory(options.Plugins.ConfigurationDirectory, basePath);
        var contexts = new List<AuthorityPluginContext>(descriptorPairs.Length);

        foreach (var (name, descriptor) in descriptorPairs)
        {
            var configPath = ResolveConfigPath(configurationDirectory, descriptor.ConfigFile);
            var optional = !descriptor.Enabled;

            if (!optional && !File.Exists(configPath))
            {
                throw new FileNotFoundException($"Required Authority plugin configuration '{configPath}' was not found.", configPath);
            }

            var builder = new ConfigurationBuilder();
            var builderBasePath = Path.GetDirectoryName(configPath);
            if (!string.IsNullOrEmpty(builderBasePath) && Directory.Exists(builderBasePath))
            {
                builder.SetBasePath(builderBasePath);
            }

            configureBuilder?.Invoke(builder);
            builder.AddYamlFile(configPath, optional: optional, reloadOnChange: false);
            var configuration = builder.Build();

            var manifest = descriptor.ToManifest(name, configPath);
            contexts.Add(new AuthorityPluginContext(manifest, configuration));
        }

        return contexts;
    }

    private static string ResolveConfigurationDirectory(string configurationDirectory, string basePath)
    {
        if (string.IsNullOrWhiteSpace(configurationDirectory))
        {
            return Path.GetFullPath(basePath);
        }

        var directory = configurationDirectory;
        if (!Path.IsPathRooted(directory))
        {
            directory = Path.Combine(basePath, directory);
        }

        return Path.GetFullPath(directory);
    }

    private static string ResolveConfigPath(string configurationDirectory, string? configFile)
    {
        if (string.IsNullOrWhiteSpace(configFile))
        {
            throw new InvalidOperationException("Authority plugin descriptor must specify a configFile.");
        }

        if (Path.IsPathRooted(configFile))
        {
            return Path.GetFullPath(configFile);
        }

        return Path.GetFullPath(Path.Combine(configurationDirectory, configFile));
    }
}
