using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Xml;
using System.Xml.Schema;

namespace StellaOps.Feedser.Source.Jvn.Internal;

internal static class JvnSchemaProvider
{
    private static readonly Lazy<(XmlSchemaSet SchemaSet, EmbeddedResourceXmlResolver Resolver)> Cached = new(
        LoadSchemas,
        LazyThreadSafetyMode.ExecutionAndPublication);

    public static XmlSchemaSet SchemaSet => Cached.Value.SchemaSet;

    private static (XmlSchemaSet SchemaSet, EmbeddedResourceXmlResolver Resolver) LoadSchemas()
    {
        var assembly = typeof(JvnSchemaProvider).GetTypeInfo().Assembly;
        var resourceMap = CreateResourceMap();
        var resolver = new EmbeddedResourceXmlResolver(assembly, resourceMap);

        var schemaSet = new XmlSchemaSet
        {
            XmlResolver = resolver,
        };

        AddSchema(schemaSet, resolver, "https://jvndb.jvn.jp/schema/vuldef_3.2.xsd");
        AddSchema(schemaSet, resolver, "https://jvndb.jvn.jp/schema/mod_sec_3.0.xsd");
        AddSchema(schemaSet, resolver, "https://jvndb.jvn.jp/schema/status_3.3.xsd");
        AddSchema(schemaSet, resolver, "https://jvndb.jvn.jp/schema/tlp_marking.xsd");
        AddSchema(schemaSet, resolver, "https://jvndb.jvn.jp/schema/data_marking.xsd");

        schemaSet.Compile();
        return (schemaSet, resolver);
    }

    private static void AddSchema(XmlSchemaSet set, EmbeddedResourceXmlResolver resolver, string uri)
    {
        using var stream = resolver.OpenStream(uri);
        using var reader = XmlReader.Create(stream, new XmlReaderSettings { XmlResolver = resolver }, uri);
        set.Add(null, reader);
    }

    private static Dictionary<string, string> CreateResourceMap()
    {
        var baseNamespace = typeof(JvnSchemaProvider).Namespace ?? "StellaOps.Feedser.Source.Jvn.Internal";
        var prefix = baseNamespace.Replace(".Internal", string.Empty, StringComparison.Ordinal);

        return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["https://jvndb.jvn.jp/schema/vuldef_3.2.xsd"] = $"{prefix}.Schemas.vuldef_3.2.xsd",
            ["vuldef_3.2.xsd"] = $"{prefix}.Schemas.vuldef_3.2.xsd",
            ["https://jvndb.jvn.jp/schema/mod_sec_3.0.xsd"] = $"{prefix}.Schemas.mod_sec_3.0.xsd",
            ["mod_sec_3.0.xsd"] = $"{prefix}.Schemas.mod_sec_3.0.xsd",
            ["https://jvndb.jvn.jp/schema/status_3.3.xsd"] = $"{prefix}.Schemas.status_3.3.xsd",
            ["status_3.3.xsd"] = $"{prefix}.Schemas.status_3.3.xsd",
            ["https://jvndb.jvn.jp/schema/tlp_marking.xsd"] = $"{prefix}.Schemas.tlp_marking.xsd",
            ["tlp_marking.xsd"] = $"{prefix}.Schemas.tlp_marking.xsd",
            ["https://jvndb.jvn.jp/schema/data_marking.xsd"] = $"{prefix}.Schemas.data_marking.xsd",
            ["data_marking.xsd"] = $"{prefix}.Schemas.data_marking.xsd",
            ["https://www.w3.org/2001/xml.xsd"] = $"{prefix}.Schemas.xml.xsd",
            ["xml.xsd"] = $"{prefix}.Schemas.xml.xsd",
        };
    }

    private sealed class EmbeddedResourceXmlResolver : XmlResolver
    {
        private readonly Assembly _assembly;
        private readonly Dictionary<string, string> _resourceMap;

        public EmbeddedResourceXmlResolver(Assembly assembly, Dictionary<string, string> resourceMap)
        {
            _assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
            _resourceMap = resourceMap ?? throw new ArgumentNullException(nameof(resourceMap));
        }

        public override ICredentials? Credentials
        {
            set { }
        }

        public Stream OpenStream(string uriOrName)
        {
            var resourceName = ResolveResourceName(uriOrName)
                ?? throw new FileNotFoundException($"Schema resource '{uriOrName}' not found in manifest.");

            var stream = _assembly.GetManifestResourceStream(resourceName);
            if (stream is null)
            {
                throw new FileNotFoundException($"Embedded schema '{resourceName}' could not be opened.");
            }

            return stream;
        }

        public override object? GetEntity(Uri absoluteUri, string? role, Type? ofObjectToReturn)
        {
            if (absoluteUri is null)
            {
                throw new ArgumentNullException(nameof(absoluteUri));
            }

            var resourceName = ResolveResourceName(absoluteUri.AbsoluteUri)
                ?? ResolveResourceName(absoluteUri.AbsolutePath.TrimStart('/'))
                ?? ResolveResourceName(Path.GetFileName(absoluteUri.AbsolutePath))
                ?? throw new FileNotFoundException($"Schema resource for '{absoluteUri}' not found.");

            var stream = _assembly.GetManifestResourceStream(resourceName);
            if (stream is null)
            {
                throw new FileNotFoundException($"Embedded schema '{resourceName}' could not be opened.");
            }

            return stream;
        }

        public override Uri ResolveUri(Uri? baseUri, string? relativeUri)
        {
            if (string.IsNullOrWhiteSpace(relativeUri))
            {
                return base.ResolveUri(baseUri, relativeUri);
            }

            if (Uri.TryCreate(relativeUri, UriKind.Absolute, out var absolute))
            {
                return absolute;
            }

            if (baseUri is not null && Uri.TryCreate(baseUri, relativeUri, out var combined))
            {
                return combined;
            }

            if (_resourceMap.ContainsKey(relativeUri))
            {
                return new Uri($"embedded:///{relativeUri}", UriKind.Absolute);
            }

            return base.ResolveUri(baseUri, relativeUri);
        }

        private string? ResolveResourceName(string? key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                return null;
            }

            if (_resourceMap.TryGetValue(key, out var resource))
            {
                return resource;
            }

            var fileName = Path.GetFileName(key);
            if (!string.IsNullOrEmpty(fileName) && _resourceMap.TryGetValue(fileName, out resource))
            {
                return resource;
            }

            return null;
        }
    }
}
