using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace StellaOps.Feedser.Source.Vndr.Oracle.Internal;

internal static class OracleDtoValidator
{
    private const int MaxAdvisoryIdLength = 128;
    private const int MaxTitleLength = 512;
    private const int MaxContentLength = 200_000;
    private const int MaxReferenceCount = 100;
    private const int MaxCveCount = 1_024;
    private const int MaxAffectedCount = 2_048;
    private const int MaxPatchDocumentCount = 512;
    private const int MaxProductLength = 512;
    private const int MaxComponentLength = 512;
    private const int MaxSupportedVersionsLength = 4_096;
    private const int MaxNotesLength = 1_024;
    private const int MaxPatchTitleLength = 512;
    private const int MaxPatchUrlLength = 1_024;
    private static readonly Regex CveRegex = new("CVE-\\d{4}-\\d{3,7}", RegexOptions.IgnoreCase | RegexOptions.Compiled);

    public static bool TryNormalize(OracleDto dto, out OracleDto normalized, out string? failureReason)
    {
        ArgumentNullException.ThrowIfNull(dto);

        failureReason = null;
        normalized = dto;

        var advisoryId = dto.AdvisoryId?.Trim();
        if (string.IsNullOrWhiteSpace(advisoryId))
        {
            failureReason = "AdvisoryId is required.";
            return false;
        }

        if (advisoryId.Length > MaxAdvisoryIdLength)
        {
            failureReason = $"AdvisoryId exceeds {MaxAdvisoryIdLength} characters.";
            return false;
        }

        var title = string.IsNullOrWhiteSpace(dto.Title) ? advisoryId : dto.Title.Trim();
        if (title.Length > MaxTitleLength)
        {
            title = title.Substring(0, MaxTitleLength);
        }

        var detailUrlRaw = dto.DetailUrl?.Trim();
        if (string.IsNullOrWhiteSpace(detailUrlRaw) || !Uri.TryCreate(detailUrlRaw, UriKind.Absolute, out var detailUri))
        {
            failureReason = "DetailUrl must be an absolute URI.";
            return false;
        }

        if (!string.Equals(detailUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(detailUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
        {
            failureReason = "DetailUrl must use HTTP or HTTPS.";
            return false;
        }

        if (dto.Published == default)
        {
            failureReason = "Published timestamp is required.";
            return false;
        }

        var published = dto.Published.ToUniversalTime();
        var content = dto.Content?.Trim() ?? string.Empty;
        if (string.IsNullOrWhiteSpace(content))
        {
            failureReason = "Advisory content is empty.";
            return false;
        }

        if (content.Length > MaxContentLength)
        {
            content = content.Substring(0, MaxContentLength);
        }

        var references = NormalizeReferences(dto.References);
        var cveIds = NormalizeCveIds(dto.CveIds);
        var affected = NormalizeAffected(dto.Affected);
        var patchDocuments = NormalizePatchDocuments(dto.PatchDocuments);

        normalized = dto with
        {
            AdvisoryId = advisoryId,
            Title = title,
            DetailUrl = detailUri.ToString(),
            Published = published,
            Content = content,
            References = references,
            CveIds = cveIds,
            Affected = affected,
            PatchDocuments = patchDocuments,
        };

        return true;
    }

    private static IReadOnlyList<string> NormalizeReferences(IReadOnlyList<string>? references)
    {
        if (references is null || references.Count == 0)
        {
            return Array.Empty<string>();
        }

        var normalized = new List<string>(Math.Min(references.Count, MaxReferenceCount));
        foreach (var reference in references.Where(static reference => !string.IsNullOrWhiteSpace(reference)))
        {
            var trimmed = reference.Trim();
            if (Uri.TryCreate(trimmed, UriKind.Absolute, out var uri)
                && (string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
                    || string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)))
            {
                normalized.Add(uri.ToString());
            }

            if (normalized.Count >= MaxReferenceCount)
            {
                break;
            }
        }

        if (normalized.Count == 0)
        {
            return Array.Empty<string>();
        }

        return normalized
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .OrderBy(static url => url, StringComparer.OrdinalIgnoreCase)
            .ToArray();
    }

    private static IReadOnlyList<string> NormalizeCveIds(IReadOnlyList<string>? cveIds)
    {
        if (cveIds is null || cveIds.Count == 0)
        {
            return Array.Empty<string>();
        }

        var normalized = new List<string>(Math.Min(cveIds.Count, MaxCveCount));
        foreach (var cve in cveIds.Where(static value => !string.IsNullOrWhiteSpace(value)))
        {
            var candidate = cve.Trim().ToUpperInvariant();
            if (!CveRegex.IsMatch(candidate))
            {
                continue;
            }

            normalized.Add(candidate);
            if (normalized.Count >= MaxCveCount)
            {
                break;
            }
        }

        if (normalized.Count == 0)
        {
            return Array.Empty<string>();
        }

        return normalized
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .OrderBy(static value => value, StringComparer.OrdinalIgnoreCase)
            .ToArray();
    }

    private static IReadOnlyList<OracleAffectedEntry> NormalizeAffected(IReadOnlyList<OracleAffectedEntry>? entries)
    {
        if (entries is null || entries.Count == 0)
        {
            return Array.Empty<OracleAffectedEntry>();
        }

        var normalized = new List<OracleAffectedEntry>(Math.Min(entries.Count, MaxAffectedCount));
        foreach (var entry in entries)
        {
            if (entry is null)
            {
                continue;
            }

            var product = TrimToLength(entry.Product, MaxProductLength);
            if (string.IsNullOrWhiteSpace(product))
            {
                continue;
            }

            var component = TrimToNull(entry.Component, MaxComponentLength);
            var versions = TrimToNull(entry.SupportedVersions, MaxSupportedVersionsLength);
            var notes = TrimToNull(entry.Notes, MaxNotesLength);
            var cves = NormalizeCveIds(entry.CveIds);

            normalized.Add(new OracleAffectedEntry(product, component, versions, notes, cves));
            if (normalized.Count >= MaxAffectedCount)
            {
                break;
            }
        }

        return normalized.Count == 0 ? Array.Empty<OracleAffectedEntry>() : normalized;
    }

    private static IReadOnlyList<OraclePatchDocument> NormalizePatchDocuments(IReadOnlyList<OraclePatchDocument>? documents)
    {
        if (documents is null || documents.Count == 0)
        {
            return Array.Empty<OraclePatchDocument>();
        }

        var normalized = new List<OraclePatchDocument>(Math.Min(documents.Count, MaxPatchDocumentCount));
        foreach (var document in documents)
        {
            if (document is null)
            {
                continue;
            }

            var product = TrimToLength(document.Product, MaxProductLength);
            if (string.IsNullOrWhiteSpace(product))
            {
                continue;
            }

            var title = TrimToNull(document.Title, MaxPatchTitleLength);
            var urlRaw = TrimToLength(document.Url, MaxPatchUrlLength);
            if (string.IsNullOrWhiteSpace(urlRaw))
            {
                continue;
            }

            if (!Uri.TryCreate(urlRaw, UriKind.Absolute, out var uri)
                || (!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
                    && !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)))
            {
                continue;
            }

            normalized.Add(new OraclePatchDocument(product, title, uri.ToString()));
            if (normalized.Count >= MaxPatchDocumentCount)
            {
                break;
            }
        }

        return normalized.Count == 0 ? Array.Empty<OraclePatchDocument>() : normalized;
    }

    private static string TrimToLength(string? value, int maxLength)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        var trimmed = value.Trim();
        if (trimmed.Length <= maxLength)
        {
            return trimmed;
        }

        return trimmed[..maxLength];
    }

    private static string? TrimToNull(string? value, int maxLength)
    {
        var trimmed = TrimToLength(value, maxLength);
        return string.IsNullOrWhiteSpace(trimmed) ? null : trimmed;
    }
}
