using System.Text.RegularExpressions;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;

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

internal static class ChromiumParser
{
    private static readonly HtmlParser HtmlParser = new();
    private static readonly Regex CveRegex = new("CVE-\\d{4}-\\d{4,}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    private static readonly Regex VersionRegex = new("(?<version>\\d+\\.\\d+\\.\\d+\\.\\d+)", RegexOptions.Compiled);

    public static ChromiumDto Parse(string html, ChromiumDocumentMetadata metadata)
    {
        ArgumentException.ThrowIfNullOrEmpty(html);
        ArgumentNullException.ThrowIfNull(metadata);

        var document = HtmlParser.ParseDocument(html);
        var body = document.QuerySelector("div.post-body") ?? document.Body;
        if (body is null)
        {
            throw new InvalidOperationException("Chromium post body not found.");
        }

        var cves = ExtractCves(body);
        var versions = ExtractVersions(body);
        var platforms = versions.Select(static v => v.Platform).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
        var references = ExtractReferences(body, metadata.DetailUrl);

        return ChromiumDto.From(metadata, cves, platforms, versions, references);
    }

    private static IReadOnlyList<string> ExtractCves(IElement body)
    {
        var matches = CveRegex.Matches(body.TextContent ?? string.Empty);
        return matches
            .Select(static match => match.Value.ToUpperInvariant())
            .Distinct(StringComparer.Ordinal)
            .OrderBy(static cve => cve, StringComparer.Ordinal)
            .ToArray();
    }

    private static IReadOnlyList<ChromiumVersionInfo> ExtractVersions(IElement body)
    {
        var results = new Dictionary<string, ChromiumVersionInfo>(StringComparer.OrdinalIgnoreCase);
        var elements = body.QuerySelectorAll("p,li");
        if (elements.Length == 0)
        {
            elements = body.QuerySelectorAll("div,span");
        }

        foreach (var element in elements)
        {
            var text = element.TextContent?.Trim();
            if (string.IsNullOrEmpty(text))
            {
                continue;
            }

            var channel = DetermineChannel(text);
            foreach (Match match in VersionRegex.Matches(text))
            {
                var version = match.Groups["version"].Value;
                var platform = DeterminePlatform(text, match);
                var key = string.Join('|', platform.ToLowerInvariant(), channel.ToLowerInvariant(), version);
                if (!results.ContainsKey(key))
                {
                    results[key] = new ChromiumVersionInfo(platform, channel, version);
                }
            }
        }

        return results.Values
            .OrderBy(static v => v.Platform, StringComparer.OrdinalIgnoreCase)
            .ThenBy(static v => v.Channel, StringComparer.OrdinalIgnoreCase)
            .ThenBy(static v => v.Version, StringComparer.Ordinal)
            .ToArray();
    }

    private static string DeterminePlatform(string text, Match match)
    {
        var after = ExtractSlice(text, match.Index + match.Length, Math.Min(120, text.Length - (match.Index + match.Length)));
        var segment = ExtractPlatformSegment(after);
        var normalized = NormalizePlatform(segment);
        if (!string.IsNullOrEmpty(normalized))
        {
            return normalized!;
        }

        var before = ExtractSlice(text, Math.Max(0, match.Index - 80), Math.Min(80, match.Index));
        normalized = NormalizePlatform(before + " " + after);
        return string.IsNullOrEmpty(normalized) ? "desktop" : normalized!;
    }

    private static string DetermineChannel(string text)
    {
        if (text.Contains("Extended Stable", StringComparison.OrdinalIgnoreCase))
        {
            return "extended-stable";
        }

        if (text.Contains("Beta", StringComparison.OrdinalIgnoreCase))
        {
            return "beta";
        }

        if (text.Contains("Dev", StringComparison.OrdinalIgnoreCase))
        {
            return "dev";
        }

        return "stable";
    }

    private static string ExtractSlice(string text, int start, int length)
    {
        if (length <= 0)
        {
            return string.Empty;
        }

        return text.Substring(start, length);
    }

    private static string ExtractPlatformSegment(string after)
    {
        if (string.IsNullOrEmpty(after))
        {
            return string.Empty;
        }

        var forIndex = after.IndexOf("for ", StringComparison.OrdinalIgnoreCase);
        if (forIndex < 0)
        {
            return string.Empty;
        }

        var remainder = after[(forIndex + 4)..];
        var terminatorIndex = remainder.IndexOfAny(new[] { '.', ';', '\n', '(', ')' });
        if (terminatorIndex >= 0)
        {
            remainder = remainder[..terminatorIndex];
        }

        var digitIndex = remainder.IndexOfAny("0123456789".ToCharArray());
        if (digitIndex >= 0)
        {
            remainder = remainder[..digitIndex];
        }

        var whichIndex = remainder.IndexOf(" which", StringComparison.OrdinalIgnoreCase);
        if (whichIndex >= 0)
        {
            remainder = remainder[..whichIndex];
        }

        return remainder.Trim();
    }

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

        var normalized = value.Replace("/", " ", StringComparison.OrdinalIgnoreCase)
            .Replace(" and ", " ", StringComparison.OrdinalIgnoreCase)
            .Replace("&", " ", StringComparison.OrdinalIgnoreCase)
            .Trim();

        if (normalized.Contains("android", StringComparison.OrdinalIgnoreCase))
        {
            return "android";
        }

        if (normalized.Contains("chromeos flex", StringComparison.OrdinalIgnoreCase))
        {
            return "chromeos-flex";
        }

        if (normalized.Contains("chromeos", StringComparison.OrdinalIgnoreCase) || normalized.Contains("chrome os", StringComparison.OrdinalIgnoreCase))
        {
            return "chromeos";
        }

        if (normalized.Contains("linux", StringComparison.OrdinalIgnoreCase))
        {
            return "linux";
        }

        var hasWindows = normalized.Contains("windows", StringComparison.OrdinalIgnoreCase);
        var hasMac = normalized.Contains("mac", StringComparison.OrdinalIgnoreCase);

        if (hasWindows && hasMac)
        {
            return "windows-mac";
        }

        if (hasWindows)
        {
            return "windows";
        }

        if (hasMac)
        {
            return "mac";
        }

        return null;
    }

    private static IReadOnlyList<ChromiumReference> ExtractReferences(IElement body, Uri detailUri)
    {
        var references = new Dictionary<string, ChromiumReference>(StringComparer.OrdinalIgnoreCase);
        foreach (var anchor in body.QuerySelectorAll("a[href]"))
        {
            var href = anchor.GetAttribute("href");
            if (string.IsNullOrWhiteSpace(href))
            {
                continue;
            }

            if (!Uri.TryCreate(href.Trim(), UriKind.Absolute, out var linkUri))
            {
                continue;
            }

            if (string.Equals(linkUri.AbsoluteUri, detailUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            if (!string.Equals(linkUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
                && !string.Equals(linkUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var kind = ClassifyReference(linkUri);
            var label = anchor.TextContent?.Trim();

            if (!references.ContainsKey(linkUri.AbsoluteUri))
            {
                references[linkUri.AbsoluteUri] = new ChromiumReference(linkUri.AbsoluteUri, kind, string.IsNullOrWhiteSpace(label) ? null : label);
            }
        }

        return references.Values
            .OrderBy(static r => r.Url, StringComparer.Ordinal)
            .ThenBy(static r => r.Kind, StringComparer.Ordinal)
            .ToArray();
    }

    private static string ClassifyReference(Uri uri)
    {
        var host = uri.Host;
        if (host.Contains("googlesource.com", StringComparison.OrdinalIgnoreCase))
        {
            return "changelog";
        }

        if (host.Contains("issues.chromium.org", StringComparison.OrdinalIgnoreCase)
            || host.Contains("bugs.chromium.org", StringComparison.OrdinalIgnoreCase)
            || host.Contains("crbug.com", StringComparison.OrdinalIgnoreCase))
        {
            return "bug";
        }

        if (host.Contains("chromium.org", StringComparison.OrdinalIgnoreCase))
        {
            return "doc";
        }

        if (host.Contains("google.com", StringComparison.OrdinalIgnoreCase))
        {
            return "google";
        }

        return "reference";
    }
}
