using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Sbroenne.ExcelMcp.Generators.Cli;

/// <summary>
/// Generates CLI command classes and registration.
/// Discovers [ServiceCategory] interfaces from referenced assemblies (Core)
/// to eliminate hard-coded category lists.
/// </summary>
[Generator]
public class CliSettingsGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Scan the compilation (including referenced assemblies) for [ServiceCategory] interfaces
        context.RegisterSourceOutput(context.CompilationProvider,
            static (spc, compilation) =>
            {
                var categories = DiscoverCategories(compilation);
                if (categories.Count == 0)
                    return;

                foreach (var (_, registryName, requiresSession) in categories)
                {
                    var cmdCode = GenerateCommandClass(registryName, requiresSession);
                    spc.AddSource($"CliCommand.{registryName}.g.cs", SourceText.From(cmdCode, Encoding.UTF8));
                }

                var regCode = GenerateRegistration(categories);
                spc.AddSource("CliCommandRegistration.Generated.g.cs", SourceText.From(regCode, Encoding.UTF8));
            });
    }

    /// <summary>
    /// Discovers [ServiceCategory] interfaces from referenced assemblies.
    /// This replaces the hard-coded category list — adding a new [ServiceCategory]
    /// interface to Core automatically generates the corresponding CLI command.
    /// Uses string-based attribute matching for cross-compilation robustness.
    /// </summary>
    private static List<(string CliName, string RegistryName, bool RequiresSession)> DiscoverCategories(Compilation compilation)
    {
        var result = new List<(string CliName, string RegistryName, bool RequiresSession)>();

        // Search referenced assemblies for interfaces with [ServiceCategory]
        foreach (var reference in compilation.References)
        {
            if (compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assembly)
                continue;

            foreach (var type in GetAllTypes(assembly.GlobalNamespace))
            {
                if (type.TypeKind != TypeKind.Interface)
                    continue;

                // Use string-based matching (robust across compilation boundaries)
                var attr = type.GetAttributes().FirstOrDefault(a =>
                    a.AttributeClass?.Name == "ServiceCategoryAttribute" &&
                    a.AttributeClass?.ContainingNamespace?.ToDisplayString() == "Sbroenne.ExcelMcp.Core.Attributes");
                if (attr == null || attr.ConstructorArguments.Length < 2)
                    continue;

                var categoryPascal = attr.ConstructorArguments[1].Value?.ToString() ?? "";

                // Get McpTool name for deriving CLI command name
                var mcpToolAttr = type.GetAttributes().FirstOrDefault(a =>
                    a.AttributeClass?.Name == "McpToolAttribute");
                var mcpToolName = mcpToolAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString()
                    ?? $"{attr.ConstructorArguments[0].Value}";

                // Check for NoSession attribute
                var noSession = type.GetAttributes().Any(a =>
                    a.AttributeClass?.Name == "NoSessionAttribute");

                var cliName = mcpToolName.Replace("_", "");
                result.Add((cliName, categoryPascal, !noSession));
            }
        }

        return result;
    }

    private static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol ns)
    {
        foreach (var type in ns.GetTypeMembers())
            yield return type;
        foreach (var child in ns.GetNamespaceMembers())
            foreach (var type in GetAllTypes(child))
                yield return type;
    }

    private static string GenerateCommandClass(string registryName, bool requiresSession)
    {
        var sb = new StringBuilder();
        sb.AppendLine("// <auto-generated />");
        sb.AppendLine("#nullable enable");
        sb.AppendLine();
        sb.AppendLine("using Sbroenne.ExcelMcp.CLI.Infrastructure;");
        sb.AppendLine("using Sbroenne.ExcelMcp.Generated;");
        sb.AppendLine();
        sb.AppendLine("namespace Sbroenne.ExcelMcp.CLI.Generated;");
        sb.AppendLine();
        sb.AppendLine($"internal sealed class {registryName}Command : ServiceCommandBase<ServiceRegistry.{registryName}.CliSettings>");
        sb.AppendLine("{");
        if (!requiresSession) sb.AppendLine("    protected override bool RequiresSession => false;");

        // NoSession commands don't have SessionId in CliSettings — return null
        if (requiresSession)
            sb.AppendLine($"    protected override string? GetSessionId(ServiceRegistry.{registryName}.CliSettings settings) => settings.SessionId;");
        else
            sb.AppendLine($"    protected override string? GetSessionId(ServiceRegistry.{registryName}.CliSettings settings) => null;");

        sb.AppendLine($"    protected override string? GetAction(ServiceRegistry.{registryName}.CliSettings settings) => settings.Action;");
        sb.AppendLine($"    protected override IReadOnlyList<string> ValidActions => ServiceRegistry.{registryName}.ValidActions;");
        sb.AppendLine($"    protected override (string command, object? args) Route(ServiceRegistry.{registryName}.CliSettings settings, string action) => ServiceRegistry.{registryName}.RouteFromSettings(action, settings);");
        sb.AppendLine("}");
        return sb.ToString();
    }

    private static string GenerateRegistration(List<(string CliName, string RegistryName, bool RequiresSession)> categories)
    {
        var sb = new StringBuilder();
        sb.AppendLine("// <auto-generated />");
        sb.AppendLine("#nullable enable");
        sb.AppendLine();
        sb.AppendLine("using Sbroenne.ExcelMcp.Generated;");
        sb.AppendLine("using Spectre.Console;");
        sb.AppendLine("using Spectre.Console.Cli;");
        sb.AppendLine();
        sb.AppendLine("namespace Sbroenne.ExcelMcp.CLI.Generated;");
        sb.AppendLine();
        sb.AppendLine("internal static class CliCommandRegistration");
        sb.AppendLine("{");
        sb.AppendLine("    public static void RegisterCommands(IConfigurator config)");
        sb.AppendLine("    {");
        foreach (var (cliName, registryName, _) in categories.OrderBy(c => c.CliName))
            sb.AppendLine($"        config.AddCommand<{registryName}Command>(\"{cliName}\").WithDescription(ServiceRegistry.{registryName}.Description.EscapeMarkup());");
        sb.AppendLine("    }");
        sb.AppendLine("}");
        return sb.ToString();
    }
}
