using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Xml.Schema;
using Microsoft.Extensions.Logging;

namespace StellaOps.Feedser.Source.Common.Xml;

public sealed class XmlSchemaValidator : IXmlSchemaValidator
{
    private readonly ILogger<XmlSchemaValidator> _logger;

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

    public void Validate(XDocument document, XmlSchemaSet schemaSet, string documentName)
    {
        ArgumentNullException.ThrowIfNull(document);
        ArgumentNullException.ThrowIfNull(schemaSet);
        ArgumentException.ThrowIfNullOrWhiteSpace(documentName);

        var errors = new List<XmlSchemaValidationError>();

        void Handler(object? sender, ValidationEventArgs args)
        {
            if (args is null)
            {
                return;
            }

            var location = FormatLocation(args.Exception);
            errors.Add(new XmlSchemaValidationError(args.Message, location));
        }

        try
        {
            document.Validate(schemaSet, Handler, addSchemaInfo: true);
        }
        catch (System.Xml.Schema.XmlSchemaValidationException ex)
        {
            var location = FormatLocation(ex);
            errors.Add(new XmlSchemaValidationError(ex.Message, location));
        }

        if (errors.Count > 0)
        {
            var exception = new XmlSchemaValidationException(documentName, errors);
            _logger.LogError(exception, "XML schema validation failed for {DocumentName}", documentName);
            throw exception;
        }

        _logger.LogDebug("XML schema validation succeeded for {DocumentName}", documentName);
    }

    private static string? FormatLocation(System.Xml.Schema.XmlSchemaException? exception)
    {
        if (exception is null)
        {
            return null;
        }

        if (exception.LineNumber <= 0)
        {
            return null;
        }

        return $"line {exception.LineNumber}, position {exception.LinePosition}";
    }
}
