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

namespace StellaOps.Feedser.Source.CertIn.Internal;

internal static class CertInDetailParser
{
    private static readonly Regex CveRegex = new("CVE-\\d{4}-\\d+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
    private static readonly Regex SeverityRegex = new("Severity\\s*[:\\-]\\s*(?<value>[A-Za-z ]{1,32})", RegexOptions.IgnoreCase | RegexOptions.Compiled);
    private static readonly Regex VendorRegex = new("(?:Vendor|Organisation|Organization|Company)\\s*[:\\-]\\s*(?<value>[^\\n\\r]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
    private static readonly Regex LinkRegex = new("href=\"(https?://[^\"]+)\"", RegexOptions.IgnoreCase | RegexOptions.Compiled);

    public static CertInAdvisoryDto Parse(CertInListingItem listing, byte[] rawHtml)
    {
        ArgumentNullException.ThrowIfNull(listing);

        var html = Encoding.UTF8.GetString(rawHtml);
        var content = HtmlToPlainText(html);
        var summary = listing.Summary ?? ExtractSummary(content);
        var severity = ExtractSeverity(content);
        var cves = ExtractCves(listing.Title, summary, content);
        var vendors = ExtractVendors(summary, content);
        var references = ExtractLinks(html);

        return new CertInAdvisoryDto(
            listing.AdvisoryId,
            listing.Title,
            listing.DetailUri.ToString(),
            listing.Published,
            summary,
            content,
            severity,
            cves,
            vendors,
            references);
    }

    private static string HtmlToPlainText(string html)
    {
        if (string.IsNullOrWhiteSpace(html))
        {
            return string.Empty;
        }

        var withoutScripts = Regex.Replace(html, "<script[\\s\\S]*?</script>", string.Empty, RegexOptions.IgnoreCase);
        var withoutStyles = Regex.Replace(withoutScripts, "<style[\\s\\S]*?</style>", string.Empty, RegexOptions.IgnoreCase);
        var withoutComments = Regex.Replace(withoutStyles, "<!--.*?-->", string.Empty, RegexOptions.Singleline);
        var withoutTags = Regex.Replace(withoutComments, "<[^>]+>", " ");
        var decoded = System.Net.WebUtility.HtmlDecode(withoutTags);
        return string.IsNullOrWhiteSpace(decoded)
            ? string.Empty
            : Regex.Replace(decoded, "\\s+", " ").Trim();
    }

    private static string? ExtractSummary(string content)
    {
        if (string.IsNullOrWhiteSpace(content))
        {
            return null;
        }

        var sentenceTerminators = new[] { ".", "!", "?" };
        foreach (var terminator in sentenceTerminators)
        {
            var index = content.IndexOf(terminator, StringComparison.Ordinal);
            if (index > 0)
            {
                return content[..(index + terminator.Length)].Trim();
            }
        }

        return content.Length > 280 ? content[..280].Trim() : content;
    }

    private static string? ExtractSeverity(string content)
    {
        var match = SeverityRegex.Match(content);
        if (match.Success)
        {
            return match.Groups["value"].Value.Trim().ToLowerInvariant();
        }

        return null;
    }

    private static ImmutableArray<string> ExtractCves(string title, string? summary, string content)
    {
        var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        void Capture(string? text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return;
            }

            foreach (Match match in CveRegex.Matches(text))
            {
                if (match.Success)
                {
                    set.Add(match.Value.ToUpperInvariant());
                }
            }
        }

        Capture(title);
        Capture(summary);
        Capture(content);

        return set.OrderBy(static value => value, StringComparer.Ordinal).ToImmutableArray();
    }

    private static ImmutableArray<string> ExtractVendors(string? summary, string content)
    {
        var vendors = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        void Add(string? value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return;
            }

            var cleaned = value
                .Replace("’", "'", StringComparison.Ordinal)
                .Trim();

            if (cleaned.Length > 200)
            {
                cleaned = cleaned[..200];
            }

            if (!string.IsNullOrWhiteSpace(cleaned))
            {
                vendors.Add(cleaned);
            }
        }

        if (!string.IsNullOrWhiteSpace(summary))
        {
            foreach (Match match in VendorRegex.Matches(summary))
            {
                Add(match.Groups["value"].Value);
            }
        }

        foreach (Match match in VendorRegex.Matches(content))
        {
            Add(match.Groups["value"].Value);
        }

        if (vendors.Count == 0 && !string.IsNullOrWhiteSpace(summary))
        {
            var fallback = summary.Split('.', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
            Add(fallback);
        }

        return vendors.Count == 0
            ? ImmutableArray<string>.Empty
            : vendors.OrderBy(static value => value, StringComparer.Ordinal).ToImmutableArray();
    }

    private static ImmutableArray<string> ExtractLinks(string html)
    {
        if (string.IsNullOrWhiteSpace(html))
        {
            return ImmutableArray<string>.Empty;
        }

        var links = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (Match match in LinkRegex.Matches(html))
        {
            if (match.Success)
            {
                links.Add(match.Groups[1].Value);
            }
        }

        return links.Count == 0
            ? ImmutableArray<string>.Empty
            : links.OrderBy(static value => value, StringComparer.Ordinal).ToImmutableArray();
    }
}
