using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Globalization;
using System.Xml.Linq;

namespace StellaOps.Feedser.Source.Ru.Bdu.Internal;

internal static class RuBduXmlParser
{
    public static RuBduVulnerabilityDto? TryParse(XElement element)
    {
        ArgumentNullException.ThrowIfNull(element);

        var identifier = element.Element("identifier")?.Value?.Trim();
        if (string.IsNullOrWhiteSpace(identifier))
        {
            return null;
        }

        var name = Normalize(element.Element("name")?.Value);
        var description = Normalize(element.Element("description")?.Value);
        var solution = Normalize(element.Element("solution")?.Value);
        var severity = Normalize(element.Element("severity")?.Value);
        var exploitStatus = Normalize(element.Element("exploit_status")?.Value);
        var fixStatus = Normalize(element.Element("fix_status")?.Value);
        var vulStatus = Normalize(element.Element("vul_status")?.Value);
        var vulClass = Normalize(element.Element("vul_class")?.Value);
        var vulState = Normalize(element.Element("vul_state")?.Value);
        var other = Normalize(element.Element("other")?.Value);
        var incidentCount = ParseInt(element.Element("vul_incident")?.Value);

        var identifyDate = ParseDate(element.Element("identify_date")?.Value);

        var cvssVectorElement = element.Element("cvss")?.Element("vector");
        var cvssVector = Normalize(cvssVectorElement?.Value);
        var cvssScore = ParseDouble(cvssVectorElement?.Attribute("score")?.Value);

        var cvss3VectorElement = element.Element("cvss3")?.Element("vector");
        var cvss3Vector = Normalize(cvss3VectorElement?.Value);
        var cvss3Score = ParseDouble(cvss3VectorElement?.Attribute("score")?.Value);

        if (string.IsNullOrWhiteSpace(cvssVector))
        {
            cvssVector = null;
            cvssScore = null;
        }

        if (string.IsNullOrWhiteSpace(cvss3Vector))
        {
            cvss3Vector = null;
            cvss3Score = null;
        }

        var software = ParseSoftware(element.Element("vulnerable_software"));
        var environment = ParseEnvironment(element.Element("environment"));
        var cwes = ParseCwes(element.Element("cwes"));
        var sources = ParseSources(element.Element("sources"));
        var identifiers = ParseIdentifiers(element.Element("identifiers"));

        return new RuBduVulnerabilityDto(
            identifier.Trim(),
            name,
            description,
            solution,
            identifyDate,
            severity,
            cvssVector,
            cvssScore,
            cvss3Vector,
            cvss3Score,
            exploitStatus,
            incidentCount,
            fixStatus,
            vulStatus,
            vulClass,
            vulState,
            other,
            software,
            environment,
            cwes,
            sources,
            identifiers);
    }

    private static ImmutableArray<RuBduSoftwareDto> ParseSoftware(XElement? root)
    {
        if (root is null)
        {
            return ImmutableArray<RuBduSoftwareDto>.Empty;
        }

        var builder = ImmutableArray.CreateBuilder<RuBduSoftwareDto>();
        foreach (var soft in root.Elements("soft"))
        {
            var vendor = Normalize(soft.Element("vendor")?.Value);
            var name = Normalize(soft.Element("name")?.Value);
            var version = Normalize(soft.Element("version")?.Value);
            var platform = Normalize(soft.Element("platform")?.Value);
            var types = soft.Element("types") is { } typesElement
                ? typesElement.Elements("type").Select(static x => Normalize(x.Value)).Where(static value => !string.IsNullOrWhiteSpace(value)).Cast<string>().ToImmutableArray()
                : ImmutableArray<string>.Empty;

            builder.Add(new RuBduSoftwareDto(vendor, name, version, platform, types));
        }

        return builder.ToImmutable();
    }

    private static ImmutableArray<RuBduEnvironmentDto> ParseEnvironment(XElement? root)
    {
        if (root is null)
        {
            return ImmutableArray<RuBduEnvironmentDto>.Empty;
        }

        var builder = ImmutableArray.CreateBuilder<RuBduEnvironmentDto>();
        foreach (var os in root.Elements())
        {
            var vendor = Normalize(os.Element("vendor")?.Value);
            var name = Normalize(os.Element("name")?.Value);
            var version = Normalize(os.Element("version")?.Value);
            var platform = Normalize(os.Element("platform")?.Value);
            builder.Add(new RuBduEnvironmentDto(vendor, name, version, platform));
        }

        return builder.ToImmutable();
    }

    private static ImmutableArray<RuBduCweDto> ParseCwes(XElement? root)
    {
        if (root is null)
        {
            return ImmutableArray<RuBduCweDto>.Empty;
        }

        var builder = ImmutableArray.CreateBuilder<RuBduCweDto>();
        foreach (var cwe in root.Elements("cwe"))
        {
            var identifier = Normalize(cwe.Element("identifier")?.Value);
            if (string.IsNullOrWhiteSpace(identifier))
            {
                continue;
            }

            var name = Normalize(cwe.Element("name")?.Value);
            builder.Add(new RuBduCweDto(identifier, name));
        }

        return builder.ToImmutable();
    }

    private static ImmutableArray<string> ParseSources(XElement? root)
    {
        if (root is null)
        {
            return ImmutableArray<string>.Empty;
        }

        var raw = root.Value;
        if (string.IsNullOrWhiteSpace(raw))
        {
            return ImmutableArray<string>.Empty;
        }

        var tokens = raw
            .Split(SourceSeparators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Select(static token => token.Trim())
            .Where(static token => !string.IsNullOrWhiteSpace(token))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToImmutableArray();

        return tokens.IsDefaultOrEmpty ? ImmutableArray<string>.Empty : tokens;
    }

    private static ImmutableArray<RuBduExternalIdentifierDto> ParseIdentifiers(XElement? root)
    {
        if (root is null)
        {
            return ImmutableArray<RuBduExternalIdentifierDto>.Empty;
        }

        var builder = ImmutableArray.CreateBuilder<RuBduExternalIdentifierDto>();
        foreach (var identifier in root.Elements("identifier"))
        {
            var value = Normalize(identifier?.Value);
            if (string.IsNullOrWhiteSpace(value))
            {
                continue;
            }

            var type = identifier?.Attribute("type")?.Value?.Trim();
            var link = identifier?.Attribute("link")?.Value?.Trim();

            if (string.IsNullOrWhiteSpace(type))
            {
                type = "external";
            }

            builder.Add(new RuBduExternalIdentifierDto(type, value.Trim(), string.IsNullOrWhiteSpace(link) ? null : link));
        }

        return builder.ToImmutable();
    }

    private static readonly char[] SourceSeparators = { '\r', '\n', '\t', ' ' };

    private static DateTimeOffset? ParseDate(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var trimmed = value.Trim();
        if (DateTimeOffset.TryParse(trimmed, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var isoDate))
        {
            return isoDate;
        }

        if (DateTimeOffset.TryParseExact(trimmed, new[] { "dd.MM.yyyy", "dd.MM.yyyy HH:mm:ss" }, CultureInfo.GetCultureInfo("ru-RU"), DateTimeStyles.AssumeUniversal, out var ruDate))
        {
            return ruDate;
        }

        return null;
    }

    private static double? ParseDouble(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        if (double.TryParse(value.Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var parsed))
        {
            return parsed;
        }

        return null;
    }

    private static int? ParseInt(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        if (int.TryParse(value.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed))
        {
            return parsed;
        }

        return null;
    }

    private static string? Normalize(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        return value.Replace('\r', ' ').Replace('\n', ' ').Trim();
    }
}
