using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json.Nodes;
using Json.Schema;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Questionable.QuestPathGenerator;

public static class Utils
{
    public static List<AdditionalText> RegisterSchemas(GeneratorExecutionContext context)
    {
        var commonSchemaFile = context.AdditionalFiles.Single(x => Path.GetFileName(x.Path) == "common-schema.json");
        var gatheringSchemaFile =
            context.AdditionalFiles.SingleOrDefault(x => Path.GetFileName(x.Path) == "gatheringlocation-v1.json");
        var questSchemaFile = context.AdditionalFiles.SingleOrDefault(x => Path.GetFileName(x.Path) == "quest-v1.json");

        SchemaRegistry.Global.Register(
            new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json"),
            JsonSchema.FromText(commonSchemaFile.GetText()!.ToString()));

        if (gatheringSchemaFile != null)
        {
            SchemaRegistry.Global.Register(
                new Uri(
                    "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json"),
                JsonSchema.FromText(gatheringSchemaFile.GetText()!.ToString()));
        }

        if (questSchemaFile != null)
        {
            SchemaRegistry.Global.Register(
                new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json"),
                JsonSchema.FromText(questSchemaFile.GetText()!.ToString()));
        }

        List<AdditionalText?> jsonSchemaFiles = [commonSchemaFile, gatheringSchemaFile, questSchemaFile];
        return jsonSchemaFiles.Where(x => x != null).Cast<AdditionalText>().ToList();
    }

    public static IEnumerable<(T, JsonNode)> GetAdditionalFiles<T>(GeneratorExecutionContext context,
        List<AdditionalText> jsonSchemaFiles, JsonSchema jsonSchema, DiagnosticDescriptor invalidJson,
        Func<string, T> idParser)
    {
        foreach (var additionalFile in context.AdditionalFiles)
        {
            if (additionalFile == null || jsonSchemaFiles.Contains(additionalFile))
                continue;

            if (Path.GetExtension(additionalFile.Path) != ".json")
                continue;

            string name = Path.GetFileName(additionalFile.Path);
            if (!name.Contains("_"))
                continue;

            T id = idParser(name.Substring(0, name.IndexOf('_')));

            var text = additionalFile.GetText();
            if (text == null)
                continue;

            var node = JsonNode.Parse(text.ToString());
            if (node == null)
                continue;

            string? schemaLocation = node["$schema"]?.GetValue<string?>();
            if (schemaLocation == null || new Uri(schemaLocation) != jsonSchema.GetId())
                continue;

            var evaluationResult = jsonSchema.Evaluate(node, new EvaluationOptions
            {
                Culture = CultureInfo.InvariantCulture,
                OutputFormat = OutputFormat.List,
            });
            if (evaluationResult.HasErrors)
            {
                var error = Diagnostic.Create(invalidJson,
                    null,
                    Path.GetFileName(additionalFile.Path));
                context.ReportDiagnostic(error);
                continue;
            }

            yield return (id, node);
        }
    }

    public static List<MethodDeclarationSyntax> CreateMethods<TId, TQuest>(string prefix,
        List<IGrouping<string, (TId, TQuest)>> partitions,
        Func<List<(TId, TQuest)>, StatementSyntax[]> toInitializers)
    {
        List<MethodDeclarationSyntax> methods =
        [
            MethodDeclaration(
                    PredefinedType(
                        Token(SyntaxKind.VoidKeyword)),
                    Identifier(prefix))
                .WithModifiers(
                    TokenList(
                        Token(SyntaxKind.PrivateKeyword),
                        Token(SyntaxKind.StaticKeyword)))
                .WithBody(
                    Block(
                        partitions
                            .Select(x =>
                                ExpressionStatement(
                                    InvocationExpression(
                                        IdentifierName(x.Key))))))
        ];

        foreach (var partition in partitions)
        {
            methods.Add(MethodDeclaration(
                    PredefinedType(
                        Token(SyntaxKind.VoidKeyword)),
                    Identifier(partition.Key))
                .WithModifiers(
                    TokenList(
                        Token(SyntaxKind.PrivateKeyword),
                        Token(SyntaxKind.StaticKeyword)))
                .WithBody(
                    Block(toInitializers(partition.ToList()))));
        }

        return methods;
    }
}