using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using AngleSharp.Html.Parser;
using AngleSharp.Html.Dom;
using StellaOps.Feedser.Source.Common.Html;

namespace StellaOps.Feedser.Source.Ics.Cisa.Internal;

public sealed class IcsCisaFeedParser
{
    private static readonly Regex AdvisoryIdRegex = new(@"^(?<id>ICS[AM]?A?-?\d{2}-\d{3}[A-Z]?(-\d{2})?)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
    private static readonly Regex CveRegex = new(@"CVE-\d{4}-\d{4,}", RegexOptions.IgnoreCase | RegexOptions.Compiled);

    private readonly HtmlContentSanitizer _sanitizer = new();
    private readonly HtmlParser _htmlParser = new();

    public IReadOnlyCollection<IcsCisaAdvisoryDto> Parse(Stream rssStream, bool isMedicalTopic, Uri? topicUri)
    {
        if (rssStream is null)
        {
            return Array.Empty<IcsCisaAdvisoryDto>();
        }

        using var reader = XmlReader.Create(rssStream, new XmlReaderSettings
        {
            DtdProcessing = DtdProcessing.Ignore,
            IgnoreComments = true,
            IgnoreProcessingInstructions = true,
        });

        var feed = SyndicationFeed.Load(reader);
        if (feed is null || feed.Items is null)
        {
            return Array.Empty<IcsCisaAdvisoryDto>();
        }

        var advisories = new List<IcsCisaAdvisoryDto>();
        foreach (var item in feed.Items)
        {
            var dto = ConvertItem(item, isMedicalTopic, topicUri);
            if (dto is not null)
            {
                advisories.Add(dto);
            }
        }

        return advisories;
    }

    private IcsCisaAdvisoryDto? ConvertItem(SyndicationItem item, bool isMedicalTopic, Uri? topicUri)
    {
        if (item is null)
        {
            return null;
        }

        var title = item.Title?.Text?.Trim();
        if (string.IsNullOrWhiteSpace(title))
        {
            return null;
        }

        var advisoryId = ExtractAdvisoryId(title);
        if (string.IsNullOrWhiteSpace(advisoryId))
        {
            return null;
        }

        var linkUri = item.Links.FirstOrDefault()?.Uri;
        if (linkUri is null && !string.IsNullOrWhiteSpace(item.Id) && Uri.TryCreate(item.Id, UriKind.Absolute, out var fallback))
        {
            linkUri = fallback;
        }

        if (linkUri is null)
        {
            return null;
        }

        var contentHtml = ExtractContentHtml(item);
        var sanitizedHtml = _sanitizer.Sanitize(contentHtml, linkUri);
        var textContent = ExtractTextContent(sanitizedHtml);

        var aliases = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { advisoryId };
        var cveIds = ExtractCveIds(textContent, aliases);
        var vendors = ExtractList(sanitizedHtml, textContent, "Vendor");
        var products = ExtractList(sanitizedHtml, textContent, "Products");
        if (products.Count == 0)
        {
            products = ExtractList(sanitizedHtml, textContent, "Product");
        }
        var attachments = ExtractAttachments(sanitizedHtml, linkUri);
        var references = ExtractReferences(sanitizedHtml, linkUri);

        var published = item.PublishDate != DateTimeOffset.MinValue
            ? item.PublishDate.ToUniversalTime()
            : item.LastUpdatedTime.ToUniversalTime();

        var updated = item.LastUpdatedTime != DateTimeOffset.MinValue
            ? item.LastUpdatedTime.ToUniversalTime()
            : (DateTimeOffset?)null;

        return new IcsCisaAdvisoryDto
        {
            AdvisoryId = advisoryId,
            Title = title,
            Link = linkUri.ToString(),
            Summary = item.Summary?.Text?.Trim(),
            DescriptionHtml = sanitizedHtml,
            Published = published,
            Updated = updated,
            IsMedical = isMedicalTopic || advisoryId.StartsWith("ICSMA", StringComparison.OrdinalIgnoreCase),
            Aliases = aliases.ToArray(),
            CveIds = cveIds,
            Vendors = vendors,
            Products = products,
            References = references,
            Attachments = attachments,
        };
    }

    private static string ExtractAdvisoryId(string title)
    {
        if (string.IsNullOrWhiteSpace(title))
        {
            return string.Empty;
        }

        var colonIndex = title.IndexOf(':');
        var candidate = colonIndex > 0 ? title[..colonIndex] : title;
        var match = AdvisoryIdRegex.Match(candidate);
        if (match.Success)
        {
            var id = match.Groups["id"].Value.Trim();
            return id.ToUpperInvariant();
        }

        return candidate.Trim();
    }

    private static string ExtractContentHtml(SyndicationItem item)
    {
        if (item.Content is TextSyndicationContent textContent)
        {
            return textContent.Text ?? string.Empty;
        }

        if (item.Summary is not null)
        {
            return item.Summary.Text ?? string.Empty;
        }

        if (item.ElementExtensions is not null)
        {
            foreach (var extension in item.ElementExtensions)
            {
                try
                {
                    var value = extension.GetObject<string>();
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        return value;
                    }
                }
                catch
                {
                    // ignore malformed extensions
                }
            }
        }

        return string.Empty;
    }

    private static IReadOnlyCollection<string> ExtractCveIds(string text, HashSet<string> aliases)
    {
        if (string.IsNullOrWhiteSpace(text))
        {
            return Array.Empty<string>();
        }

        var matches = CveRegex.Matches(text);
        if (matches.Count == 0)
        {
            return Array.Empty<string>();
        }

        var ids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (Match match in matches)
        {
            if (!match.Success)
            {
                continue;
            }

            var value = match.Value.ToUpperInvariant();
            if (ids.Add(value))
            {
                aliases.Add(value);
            }
        }

        return ids.ToArray();
    }

    private IReadOnlyCollection<string> ExtractList(string sanitizedHtml, string textContent, string key)
    {
        if (string.IsNullOrWhiteSpace(sanitizedHtml))
        {
            return Array.Empty<string>();
        }

        var items = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        try
        {
            var document = _htmlParser.ParseDocument(sanitizedHtml);
            foreach (var element in document.All)
            {
                if (element is IHtmlParagraphElement or IHtmlDivElement or IHtmlSpanElement or IHtmlListItemElement)
                {
                    var content = element.TextContent?.Trim();
                    if (string.IsNullOrWhiteSpace(content))
                    {
                        continue;
                    }

                    if (content.StartsWith($"{key}:", StringComparison.OrdinalIgnoreCase))
                    {
                        var line = content[(key.Length + 1)..].Trim();
                        foreach (var part in line.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            var value = part.Trim();
                            if (!string.IsNullOrWhiteSpace(value))
                            {
                                items.Add(value);
                            }
                        }
                    }
                }
            }
        }
        catch
        {
            // ignore HTML parsing failures; fallback to text processing below
        }

        if (items.Count == 0 && !string.IsNullOrWhiteSpace(textContent))
        {
            using var reader = new StringReader(textContent);
            string? line;
            while ((line = reader.ReadLine()) is not null)
            {
                if (line.StartsWith($"{key}:", StringComparison.OrdinalIgnoreCase))
                {
                    var raw = line[(key.Length + 1)..].Trim();
                    foreach (var part in raw.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        var value = part.Trim();
                        if (!string.IsNullOrWhiteSpace(value))
                        {
                            items.Add(value);
                        }
                    }
                }
            }
        }

        return items.ToArray();
    }

    private IReadOnlyCollection<IcsCisaAttachmentDto> ExtractAttachments(string sanitizedHtml, Uri linkUri)
    {
        if (string.IsNullOrWhiteSpace(sanitizedHtml))
        {
            return Array.Empty<IcsCisaAttachmentDto>();
        }

        try
        {
            var document = _htmlParser.ParseDocument(sanitizedHtml);
            var attachments = new List<IcsCisaAttachmentDto>();

            foreach (var anchor in document.QuerySelectorAll("a"))
            {
                var href = anchor.GetAttribute("href");
                if (string.IsNullOrWhiteSpace(href))
                {
                    continue;
                }

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

                var url = resolved.ToString();
                if (!url.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) &&
                    !url.Contains("/pdf", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                attachments.Add(new IcsCisaAttachmentDto
                {
                    Title = anchor.TextContent?.Trim(),
                    Url = url,
                });
            }

            return attachments.Count == 0 ? Array.Empty<IcsCisaAttachmentDto>() : attachments;
        }
        catch
        {
            return Array.Empty<IcsCisaAttachmentDto>();
        }
    }

    private IReadOnlyCollection<string> ExtractReferences(string sanitizedHtml, Uri linkUri)
    {
        if (string.IsNullOrWhiteSpace(sanitizedHtml))
        {
            return new[] { linkUri.ToString() };
        }

        try
        {
            var document = _htmlParser.ParseDocument(sanitizedHtml);
            var links = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            {
                linkUri.ToString()
            };

            foreach (var anchor in document.QuerySelectorAll("a"))
            {
                var href = anchor.GetAttribute("href");
                if (string.IsNullOrWhiteSpace(href))
                {
                    continue;
                }

                if (Uri.TryCreate(linkUri, href, out var resolved))
                {
                    links.Add(resolved.ToString());
                }
            }

            return links.ToArray();
        }
        catch
        {
            return new[] { linkUri.ToString() };
        }
    }

    private string ExtractTextContent(string sanitizedHtml)
    {
        if (string.IsNullOrWhiteSpace(sanitizedHtml))
        {
            return string.Empty;
        }

        try
        {
            var document = _htmlParser.ParseDocument(sanitizedHtml);
            var builder = new StringBuilder();
            var body = document.Body ?? document.DocumentElement;
            if (body is null)
            {
                return string.Empty;
            }

            foreach (var node in body.ChildNodes)
            {
                var text = node.TextContent;
                if (string.IsNullOrWhiteSpace(text))
                {
                    continue;
                }

                if (builder.Length > 0)
                {
                    builder.AppendLine();
                }

                builder.Append(text.Trim());
            }

            return builder.ToString();
        }
        catch
        {
            return sanitizedHtml;
        }
    }
}
