using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Json.Schema;
using Microsoft.Extensions.Logging;

namespace StellaOps.Feedser.Source.Common.Json;
public sealed class JsonSchemaValidator : IJsonSchemaValidator
{
    private readonly ILogger<JsonSchemaValidator> _logger;
    private const int MaxLoggedErrors = 5;

    public JsonSchemaValidator(ILogger<JsonSchemaValidator> logger)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public void Validate(JsonDocument document, JsonSchema schema, string documentName)
    {
        ArgumentNullException.ThrowIfNull(document);
        ArgumentNullException.ThrowIfNull(schema);
        ArgumentException.ThrowIfNullOrEmpty(documentName);

        var result = schema.Evaluate(document.RootElement, new EvaluationOptions
        {
            OutputFormat = OutputFormat.List,
            RequireFormatValidation = true,
        });

        if (result.IsValid)
        {
            return;
        }

        var errors = CollectErrors(result);

        if (errors.Count == 0)
        {
            _logger.LogWarning("Schema validation failed for {Document} with unknown errors", documentName);
            throw new JsonSchemaValidationException(documentName, errors);
        }

        foreach (var violation in errors.Take(MaxLoggedErrors))
        {
            _logger.LogWarning(
                "Schema violation for {Document} at {InstanceLocation} (keyword: {Keyword}): {Message}",
                documentName,
                string.IsNullOrEmpty(violation.InstanceLocation) ? "#" : violation.InstanceLocation,
                violation.Keyword,
                violation.Message);
        }

        if (errors.Count > MaxLoggedErrors)
        {
            _logger.LogWarning("{Count} additional schema violations for {Document} suppressed", errors.Count - MaxLoggedErrors, documentName);
        }

        throw new JsonSchemaValidationException(documentName, errors);
    }

    private static IReadOnlyList<JsonSchemaValidationError> CollectErrors(EvaluationResults result)
    {
        var errors = new List<JsonSchemaValidationError>();
        Aggregate(result, errors);
        return errors;
    }

    private static void Aggregate(EvaluationResults node, List<JsonSchemaValidationError> errors)
    {
        if (node.Errors is { Count: > 0 })
        {
            foreach (var kvp in node.Errors)
            {
                errors.Add(new JsonSchemaValidationError(
                    node.InstanceLocation?.ToString() ?? string.Empty,
                    node.SchemaLocation?.ToString() ?? string.Empty,
                    kvp.Value,
                    kvp.Key));
            }
        }

        if (node.Details is null)
        {
            return;
        }

        foreach (var child in node.Details)
        {
            Aggregate(child, errors);
        }
    }
}
