using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text;
using StellaOps.Feedser.Models;
using StellaOps.Feedser.Normalization.Cvss;
using StellaOps.Feedser.Storage.Mongo.Documents;

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

internal static class RuBduMapper
{
    private const string RawVersionScheme = "ru-bdu.raw";

    public static Advisory Map(RuBduVulnerabilityDto dto, DocumentRecord document, DateTimeOffset recordedAt)
    {
        ArgumentNullException.ThrowIfNull(dto);
        ArgumentNullException.ThrowIfNull(document);

        var advisoryProvenance = new AdvisoryProvenance(
            RuBduConnectorPlugin.SourceName,
            "advisory",
            dto.Identifier,
            recordedAt,
            new[] { ProvenanceFieldMasks.Advisory });

        var aliases = BuildAliases(dto);
        var packages = BuildPackages(dto, recordedAt);
        var references = BuildReferences(dto, document, recordedAt);
        var cvssMetrics = BuildCvssMetrics(dto, recordedAt, out var severityFromCvss);
        var severity = severityFromCvss ?? NormalizeSeverity(dto.SeverityText);
        var exploitKnown = DetermineExploitKnown(dto);

        return new Advisory(
            advisoryKey: dto.Identifier,
            title: dto.Name ?? dto.Identifier,
            summary: dto.Description,
            language: "ru",
            published: dto.IdentifyDate,
            modified: dto.IdentifyDate,
            severity: severity,
            exploitKnown: exploitKnown,
            aliases: aliases,
            references: references,
            affectedPackages: packages,
            cvssMetrics: cvssMetrics,
            provenance: new[] { advisoryProvenance });
    }

    private static IReadOnlyList<string> BuildAliases(RuBduVulnerabilityDto dto)
    {
        var aliases = new HashSet<string>(StringComparer.Ordinal);

        if (!string.IsNullOrWhiteSpace(dto.Identifier))
        {
            aliases.Add(dto.Identifier.Trim());
        }

        foreach (var identifier in dto.Identifiers)
        {
            if (string.IsNullOrWhiteSpace(identifier.Value))
            {
                continue;
            }

            aliases.Add(identifier.Value.Trim());
        }

        return aliases.Count == 0 ? Array.Empty<string>() : aliases.ToArray();
    }

    private static IReadOnlyList<AffectedPackage> BuildPackages(RuBduVulnerabilityDto dto, DateTimeOffset recordedAt)
    {
        if (dto.Software.IsDefaultOrEmpty)
        {
            return Array.Empty<AffectedPackage>();
        }

        var packages = new List<AffectedPackage>(dto.Software.Length);
        foreach (var software in dto.Software)
        {
            if (string.IsNullOrWhiteSpace(software.Name) && string.IsNullOrWhiteSpace(software.Vendor))
            {
                continue;
            }

            var identifier = BuildPackageIdentifier(dto.Identifier, software);
            var packageProvenance = new AdvisoryProvenance(
                RuBduConnectorPlugin.SourceName,
                "package",
                identifier,
                recordedAt,
                new[] { ProvenanceFieldMasks.AffectedPackages });

            var statuses = BuildPackageStatuses(dto, recordedAt);
            var ranges = BuildVersionRanges(software, recordedAt);
            var normalizedVersions = BuildNormalizedVersions(software);

            packages.Add(new AffectedPackage(
                DeterminePackageType(software.Types),
                identifier,
                platform: NormalizePlatform(software.Platform),
                versionRanges: ranges,
                statuses: statuses,
                provenance: new[] { packageProvenance },
                normalizedVersions: normalizedVersions));
        }

        return packages;
    }

    private static string BuildPackageIdentifier(string fallbackIdentifier, RuBduSoftwareDto software)
    {
        var parts = new[] { software.Vendor, software.Name }
            .Where(static part => !string.IsNullOrWhiteSpace(part))
            .Select(static part => part!.Trim())
            .ToArray();

        if (parts.Length == 0)
        {
            return software.Name ?? software.Vendor ?? fallbackIdentifier;
        }

        return string.Join(" ", parts);
    }

    private static IReadOnlyList<AffectedPackageStatus> BuildPackageStatuses(RuBduVulnerabilityDto dto, DateTimeOffset recordedAt)
    {
        var statuses = new List<AffectedPackageStatus>(capacity: 2);

        if (TryNormalizeVulnerabilityStatus(dto.VulStatus, out var vulnerabilityStatus))
        {
            statuses.Add(new AffectedPackageStatus(
                vulnerabilityStatus!,
                new AdvisoryProvenance(
                    RuBduConnectorPlugin.SourceName,
                    "package-status",
                    dto.VulStatus!,
                    recordedAt,
                    new[] { ProvenanceFieldMasks.PackageStatuses })));
        }

        if (TryNormalizeFixStatus(dto.FixStatus, out var fixStatus))
        {
            statuses.Add(new AffectedPackageStatus(
                fixStatus!,
                new AdvisoryProvenance(
                    RuBduConnectorPlugin.SourceName,
                    "package-fix-status",
                    dto.FixStatus!,
                    recordedAt,
                    new[] { ProvenanceFieldMasks.PackageStatuses })));
        }

        return statuses.Count == 0 ? Array.Empty<AffectedPackageStatus>() : statuses;
    }

    private static bool TryNormalizeVulnerabilityStatus(string? status, out string? normalized)
    {
        normalized = null;
        if (string.IsNullOrWhiteSpace(status))
        {
            return false;
        }

        var token = status.Trim().ToLowerInvariant();
        if (token.Contains("потенциал", StringComparison.Ordinal))
        {
            normalized = AffectedPackageStatusCatalog.UnderInvestigation;
            return true;
        }

        if (token.Contains("подтвержд", StringComparison.Ordinal))
        {
            normalized = AffectedPackageStatusCatalog.Affected;
            return true;
        }

        if (token.Contains("актуал", StringComparison.Ordinal))
        {
            normalized = AffectedPackageStatusCatalog.Affected;
            return true;
        }

        return false;
    }

    private static bool TryNormalizeFixStatus(string? status, out string? normalized)
    {
        normalized = null;
        if (string.IsNullOrWhiteSpace(status))
        {
            return false;
        }

        var token = status.Trim().ToLowerInvariant();
        if (token.Contains("устранена", StringComparison.Ordinal))
        {
            normalized = AffectedPackageStatusCatalog.Fixed;
            return true;
        }

        if (token.Contains("информация об устранении отсутствует", StringComparison.Ordinal))
        {
            normalized = AffectedPackageStatusCatalog.Unknown;
            return true;
        }

        return false;
    }

    private static IReadOnlyList<AffectedVersionRange> BuildVersionRanges(RuBduSoftwareDto software, DateTimeOffset recordedAt)
    {
        var tokens = SplitVersionTokens(software.Version).ToArray();
        if (tokens.Length == 0)
        {
            return Array.Empty<AffectedVersionRange>();
        }

        var ranges = new List<AffectedVersionRange>(tokens.Length);
        foreach (var token in tokens)
        {
            ranges.Add(new AffectedVersionRange(
                rangeKind: "string",
                introducedVersion: null,
                fixedVersion: null,
                lastAffectedVersion: null,
                rangeExpression: token,
                provenance: new AdvisoryProvenance(
                    RuBduConnectorPlugin.SourceName,
                    "package-range",
                    token,
                    recordedAt,
                    new[] { ProvenanceFieldMasks.VersionRanges })));
        }

        return ranges;
    }

    private static IReadOnlyList<NormalizedVersionRule> BuildNormalizedVersions(RuBduSoftwareDto software)
    {
        var tokens = SplitVersionTokens(software.Version).ToArray();
        if (tokens.Length == 0)
        {
            return Array.Empty<NormalizedVersionRule>();
        }

        var rules = new List<NormalizedVersionRule>(tokens.Length);
        foreach (var token in tokens)
        {
            rules.Add(new NormalizedVersionRule(
                RawVersionScheme,
                NormalizedVersionRuleTypes.Exact,
                value: token));
        }

        return rules;
    }

    private static IEnumerable<string> SplitVersionTokens(string? version)
    {
        if (string.IsNullOrWhiteSpace(version))
        {
            yield break;
        }

        var raw = version.Trim();
        if (raw.Length == 0 || string.Equals(raw, "-", StringComparison.Ordinal))
        {
            yield break;
        }

        var tokens = raw.Split(VersionSeparators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (tokens.Length == 0)
        {
            yield return raw;
            yield break;
        }

        foreach (var token in tokens)
        {
            if (string.IsNullOrWhiteSpace(token) || string.Equals(token, "-", StringComparison.Ordinal))
            {
                continue;
            }

            if (token.Equals("не указано", StringComparison.OrdinalIgnoreCase)
                || token.Equals("не указана", StringComparison.OrdinalIgnoreCase)
                || token.Equals("не определено", StringComparison.OrdinalIgnoreCase)
                || token.Equals("не определена", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            yield return token;
        }
    }

    private static string? NormalizePlatform(string? platform)
    {
        if (string.IsNullOrWhiteSpace(platform))
        {
            return null;
        }

        var trimmed = platform.Trim();
        if (trimmed.Length == 0)
        {
            return null;
        }

        if (trimmed.Equals("-", StringComparison.Ordinal)
            || trimmed.Equals("не указана", StringComparison.OrdinalIgnoreCase)
            || trimmed.Equals("не указано", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        return trimmed;
    }

    private static string DeterminePackageType(ImmutableArray<string> types)
        => IsIcsSoftware(types) ? AffectedPackageTypes.IcsVendor : AffectedPackageTypes.Vendor;

    private static bool IsIcsSoftware(ImmutableArray<string> types)
    {
        if (types.IsDefaultOrEmpty)
        {
            return false;
        }

        foreach (var type in types)
        {
            if (string.IsNullOrWhiteSpace(type))
            {
                continue;
            }

            var token = type.Trim();
            if (token.Contains("АСУ", StringComparison.OrdinalIgnoreCase)
                || token.Contains("SCADA", StringComparison.OrdinalIgnoreCase)
                || token.Contains("ICS", StringComparison.OrdinalIgnoreCase)
                || token.Contains("промыш", StringComparison.OrdinalIgnoreCase)
                || token.Contains("industrial", StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }

        return false;
    }

    private static IReadOnlyList<AdvisoryReference> BuildReferences(RuBduVulnerabilityDto dto, DocumentRecord document, DateTimeOffset recordedAt)
    {
        var references = new List<AdvisoryReference>();
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        void AddReference(string? url, string kind, string sourceTag, string? summary = null)
        {
            if (string.IsNullOrWhiteSpace(url))
        {
            return;
        }

        var trimmed = url.Trim();
        if (!Uri.TryCreate(trimmed, UriKind.Absolute, out var uri))
        {
            if (trimmed.StartsWith("www.", StringComparison.OrdinalIgnoreCase)
                && Uri.TryCreate($"https://{trimmed}", UriKind.Absolute, out var prefixed))
            {
                uri = prefixed;
            }
            else
            {
                return;
            }
        }

        var canonical = uri.ToString();
        if (!seen.Add(canonical))
        {
                return;
            }

            references.Add(new AdvisoryReference(
                canonical,
                kind,
                sourceTag,
                summary,
                new AdvisoryProvenance(
                    RuBduConnectorPlugin.SourceName,
                    "reference",
                    canonical,
                    recordedAt,
                    new[] { ProvenanceFieldMasks.References })));
        }

        AddReference(document.Uri, "details", RuBduConnectorPlugin.SourceName);

        foreach (var source in dto.Sources)
        {
            AddReference(source, "source", RuBduConnectorPlugin.SourceName);
        }

        foreach (var identifier in dto.Identifiers)
        {
            if (string.IsNullOrWhiteSpace(identifier.Link))
            {
                continue;
            }

            var sourceTag = NormalizeIdentifierType(identifier.Type);
            var kind = string.Equals(sourceTag, "cve", StringComparison.Ordinal) ? "cve" : "external";
            AddReference(identifier.Link, kind, sourceTag, identifier.Value);
        }

        foreach (var cwe in dto.Cwes)
        {
            if (string.IsNullOrWhiteSpace(cwe.Identifier))
            {
                continue;
            }

            var slug = cwe.Identifier.ToUpperInvariant().Replace("CWE-", string.Empty, StringComparison.OrdinalIgnoreCase);
            if (!slug.All(char.IsDigit))
            {
                continue;
            }

            var url = $"https://cwe.mitre.org/data/definitions/{slug}.html";
            AddReference(url, "cwe", "cwe", cwe.Name);
        }

        return references;
    }

    private static string NormalizeIdentifierType(string? type)
    {
        if (string.IsNullOrWhiteSpace(type))
        {
            return RuBduConnectorPlugin.SourceName;
        }

        var builder = new StringBuilder(type.Length);
        foreach (var ch in type)
        {
            if (char.IsLetterOrDigit(ch))
            {
                builder.Append(char.ToLowerInvariant(ch));
            }
            else if (ch is '-' or '_' or '.')
            {
                builder.Append(ch);
            }
        }

        return builder.Length == 0 ? RuBduConnectorPlugin.SourceName : builder.ToString();
    }

    private static IReadOnlyList<CvssMetric> BuildCvssMetrics(RuBduVulnerabilityDto dto, DateTimeOffset recordedAt, out string? severity)
    {
        severity = null;
        var metrics = new List<CvssMetric>();

        if (!string.IsNullOrWhiteSpace(dto.CvssVector) && CvssMetricNormalizer.TryNormalize("2.0", dto.CvssVector, dto.CvssScore, null, out var normalized))
        {
            var provenance = new AdvisoryProvenance(
                RuBduConnectorPlugin.SourceName,
                "cvss",
                normalized.Vector,
                recordedAt,
                new[] { ProvenanceFieldMasks.CvssMetrics });
            var metric = normalized.ToModel(provenance);
            metrics.Add(metric);
        }

        if (!string.IsNullOrWhiteSpace(dto.Cvss3Vector) && CvssMetricNormalizer.TryNormalize("3.1", dto.Cvss3Vector, dto.Cvss3Score, null, out var normalized3))
        {
            var provenance = new AdvisoryProvenance(
                RuBduConnectorPlugin.SourceName,
                "cvss",
                normalized3.Vector,
                recordedAt,
                new[] { ProvenanceFieldMasks.CvssMetrics });
            var metric = normalized3.ToModel(provenance);
            metrics.Add(metric);
        }

        if (metrics.Count > 1)
        {
            metrics = metrics
                .OrderByDescending(static metric => metric.BaseScore)
                .ThenBy(static metric => metric.Version, StringComparer.Ordinal)
                .ToList();
        }

        severity = metrics.Count > 0 ? metrics[0].BaseSeverity : severity;
        return metrics;
    }

    private static string? NormalizeSeverity(string? severityText)
    {
        if (string.IsNullOrWhiteSpace(severityText))
        {
            return null;
        }

        var token = severityText.Trim().ToLowerInvariant();
        if (token.Contains("критич", StringComparison.Ordinal))
        {
            return "critical";
        }

        if (token.Contains("высок", StringComparison.Ordinal))
        {
            return "high";
        }

        if (token.Contains("средн", StringComparison.Ordinal) || token.Contains("умер", StringComparison.Ordinal))
        {
            return "medium";
        }

        if (token.Contains("низк", StringComparison.Ordinal))
        {
            return "low";
        }

        return null;
    }

    private static bool DetermineExploitKnown(RuBduVulnerabilityDto dto)
    {
        if (dto.IncidentCount.HasValue && dto.IncidentCount.Value > 0)
        {
            return true;
        }

        if (!string.IsNullOrWhiteSpace(dto.ExploitStatus))
        {
            var status = dto.ExploitStatus.Trim().ToLowerInvariant();
            if (status.Contains("существ", StringComparison.Ordinal) || status.Contains("использ", StringComparison.Ordinal))
            {
                return true;
            }
        }

        return false;
    }

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