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

namespace Eis.Infrastructure.Public;

internal sealed partial class NoticeContentFormatter
{
    private static readonly HashSet<string> AllowedTags = new(StringComparer.OrdinalIgnoreCase)
    {
        "p", "br", "h2", "h3", "h4", "strong", "em", "u", "s", "ul", "ol", "li", "blockquote", "a",
        "figure", "figcaption", "img", "table", "thead", "tbody", "tfoot", "tr", "th", "td"
    };

    private static readonly HashSet<string> DangerousTags = new(StringComparer.OrdinalIgnoreCase)
    {
        "script", "style", "iframe", "object", "embed", "template"
    };

    private static readonly HashSet<string> AllowedFigureClasses = new(StringComparer.Ordinal)
    {
        "image", "table", "image-style-inline", "image-style-block", "image-style-side",
        "image-style-align-left", "image-style-align-right", "image-style-block-align-left", "image-style-block-align-right"
    };

    public string Sanitize(string? value)
    {
        var source = (value ?? string.Empty).Trim();
        if (source.Length > 20_000)
        {
            source = source[..20_000];
        }

        var parser = new HtmlParser();
        var document = parser.ParseDocument($"<!doctype html><html><body>{source}</body></html>");
        if (document.Body is null)
        {
            return string.Empty;
        }
        SanitizeChildren(document.Body);
        return document.Body.InnerHtml;
    }

    public string ContentHtml(string? value)
    {
        var source = (value ?? string.Empty).Trim();
        if (source.Length == 0)
        {
            return string.Empty;
        }

        if (AllowedTagRegex().IsMatch(source))
        {
            return Sanitize(source);
        }

        return string.Join(string.Empty, ParagraphRegex().Split(source)
            .Select(paragraph => $"<p>{WebUtility.HtmlEncode(paragraph).Replace("\r\n", "<br>", StringComparison.Ordinal).Replace("\n", "<br>", StringComparison.Ordinal)}</p>"));
    }

    public string PlainText(string? value)
    {
        var sanitized = BlockTagRegex().Replace(Sanitize(value), " ");
        var parser = new HtmlParser();
        var document = parser.ParseDocument($"<!doctype html><html><body>{sanitized}</body></html>");
        return WhitespaceRegex().Replace(document.Body?.TextContent ?? string.Empty, " ").Trim();
    }

    private static void SanitizeChildren(INode parent)
    {
        foreach (var child in parent.ChildNodes.ToArray())
        {
            if (child.NodeType == NodeType.Comment)
            {
                child.Parent?.RemoveChild(child);
                continue;
            }
            if (child is not IElement element)
            {
                continue;
            }
            if (!AllowedTags.Contains(element.LocalName))
            {
                if (DangerousTags.Contains(element.LocalName))
                {
                    element.Parent?.RemoveChild(element);
                    continue;
                }

                SanitizeChildren(element);
                var container = element.Parent;
                if (container is null)
                {
                    continue;
                }
                foreach (var grandchild in element.ChildNodes.ToArray())
                {
                    container.InsertBefore(grandchild, element);
                }
                container.RemoveChild(element);
                continue;
            }

            SanitizeAttributes(element);
            SanitizeChildren(element);
        }
    }

    private static void SanitizeAttributes(IElement element)
    {
        var allowed = element.LocalName switch
        {
            "a" => new HashSet<string>(["href", "target", "rel"], StringComparer.OrdinalIgnoreCase),
            "figure" => new HashSet<string>(["class"], StringComparer.OrdinalIgnoreCase),
            "img" => new HashSet<string>(["src", "alt"], StringComparer.OrdinalIgnoreCase),
            "th" or "td" => new HashSet<string>(["colspan", "rowspan"], StringComparer.OrdinalIgnoreCase),
            _ => []
        };
        foreach (var attribute in element.Attributes.ToArray())
        {
            if (!allowed.Contains(attribute.Name))
            {
                element.RemoveAttribute(attribute.Name);
            }
        }

        if (element.LocalName == "a")
        {
            var href = element.GetAttribute("href");
            if (!IsSafeLink(href))
            {
                element.RemoveAttribute("href");
            }
            if (!string.Equals(element.GetAttribute("target"), "_blank", StringComparison.Ordinal))
            {
                element.RemoveAttribute("target");
            }
            element.SetAttribute("rel", "noopener noreferrer");
        }
        else if (element.LocalName == "img")
        {
            var source = element.GetAttribute("src");
            if (!Uri.TryCreate(source, UriKind.Absolute, out var uri) ||
                (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
            {
                element.RemoveAttribute("src");
            }
        }
        else if (element.LocalName == "figure")
        {
            var classes = (element.GetAttribute("class") ?? string.Empty)
                .Split(' ', StringSplitOptions.RemoveEmptyEntries)
                .Where(AllowedFigureClasses.Contains)
                .Distinct(StringComparer.Ordinal)
                .ToArray();
            if (classes.Length == 0)
            {
                element.RemoveAttribute("class");
            }
            else
            {
                element.SetAttribute("class", string.Join(' ', classes));
            }
        }
    }

    private static bool IsSafeLink(string? value)
    {
        if (string.IsNullOrWhiteSpace(value) || value.StartsWith("//", StringComparison.Ordinal))
        {
            return false;
        }
        if (value.StartsWith('/') || value.StartsWith('#') || value.StartsWith('?'))
        {
            return true;
        }
        return Uri.TryCreate(value, UriKind.Absolute, out var uri) &&
               uri.Scheme is "http" or "https" or "mailto" or "tel";
    }

    [GeneratedRegex("<\\/?(?:p|h[2-4]|strong|em|u|s|ul|ol|li|blockquote|a|br|figure|figcaption|img|table|thead|tbody|tfoot|tr|th|td)\\b", RegexOptions.IgnoreCase)]
    private static partial Regex AllowedTagRegex();

    [GeneratedRegex("\\r?\\n{2,}")]
    private static partial Regex ParagraphRegex();

    [GeneratedRegex("</?(?:p|h[2-4]|ul|ol|li|blockquote|br|figure|figcaption|table|thead|tbody|tfoot|tr|th|td)\\b[^>]*>", RegexOptions.IgnoreCase)]
    private static partial Regex BlockTagRegex();

    [GeneratedRegex("\\s+")]
    private static partial Regex WhitespaceRegex();
}
