using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using PlotLine.Models;

namespace PlotLine.Services;

public static class PublicRouteNames
{
    public const string Home = "PublicHome";
    public const string Features = "PublicFeatures";
    public const string Pricing = "PublicPricing";
    public const string Help = "PublicHelp";
    public const string HelpCategory = "PublicHelpCategory";
    public const string HelpArticle = "PublicHelpArticle";
    public const string About = "PublicAbout";
    public const string Contact = "PublicContact";
    public const string Privacy = "PublicPrivacy";
    public const string Terms = "PublicTerms";
}

public static partial class PublicSeo
{
    public const string DefaultBaseUrl = "https://plotdirector.com";
    public const string DefaultOgImagePath = "/og-image.png";
    private const int DescriptionTargetLength = 155;

    public static string BaseUrl(IConfiguration configuration)
    {
        var baseUrl = configuration["Site:PublicBaseUrl"]?.TrimEnd('/');
        if (string.IsNullOrWhiteSpace(baseUrl))
        {
            return DefaultBaseUrl;
        }

        if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var uri)
            || !string.Equals(uri.Host, "plotdirector.com", StringComparison.OrdinalIgnoreCase))
        {
            return DefaultBaseUrl;
        }

        return DefaultBaseUrl;
    }

    public static string AbsoluteUrl(IConfiguration configuration, string path) =>
        $"{BaseUrl(configuration)}{NormalisePath(path)}";

    public static string DisplayTitle(string? title)
    {
        if (string.IsNullOrWhiteSpace(title))
        {
            return "PlotDirector";
        }

        var trimmed = title.Trim();
        return string.Equals(trimmed, "PlotDirector", StringComparison.OrdinalIgnoreCase)
            || trimmed.EndsWith("| PlotDirector", StringComparison.OrdinalIgnoreCase)
            || trimmed.EndsWith("- PlotDirector", StringComparison.OrdinalIgnoreCase)
            ? trimmed
            : $"{trimmed} | PlotDirector";
    }

    public static void Apply(ViewDataDictionary viewData, IConfiguration configuration, SeoMetadata metadata)
    {
        var canonicalUrl = string.IsNullOrWhiteSpace(metadata.CanonicalUrl)
            ? AbsoluteUrl(configuration, metadata.CanonicalPath ?? "/")
            : CanonicalUrl(configuration, metadata.CanonicalUrl);
        var ogImage = string.IsNullOrWhiteSpace(metadata.OgImage)
            ? AbsoluteUrl(configuration, DefaultOgImagePath)
            : CanonicalUrl(configuration, metadata.OgImage);
        var fullTitle = DisplayTitle(metadata.Title);

        viewData["Seo"] = metadata with
        {
            CanonicalUrl = canonicalUrl,
            OgImage = ogImage
        };
        viewData["SeoTitle"] = fullTitle;
        viewData["SeoDescription"] = metadata.Description;
        viewData["SeoKeywords"] = metadata.Keywords;
        viewData["CanonicalUrl"] = canonicalUrl;
        viewData["OgTitle"] = DisplayTitle(metadata.OgTitle ?? metadata.Title);
        viewData["OgDescription"] = metadata.OgDescription ?? metadata.Description;
        viewData["OgType"] = string.IsNullOrWhiteSpace(metadata.OgType) ? "website" : metadata.OgType;
        viewData["OgImage"] = ogImage;
        viewData["TwitterCard"] = string.IsNullOrWhiteSpace(metadata.TwitterCard) ? "summary_large_image" : metadata.TwitterCard;
        viewData["StructuredData"] = metadata.JsonLd;
    }

    public static string HelpArticleDescription(HelpArticle article, IEnumerable<HelpArticle> allArticles)
    {
        var description = MetaDescription(article.QuickSummary, article.MarkdownContent);
        var duplicates = allArticles
            .Where(other => !ReferenceEquals(other, article))
            .Select(other => MetaDescription(other.QuickSummary, other.MarkdownContent))
            .Any(otherDescription => string.Equals(otherDescription, description, StringComparison.OrdinalIgnoreCase));

        if (!duplicates)
        {
            return description;
        }

        var fallback = $"{article.Title}: {description}";
        return TrimDescription(fallback);
    }

    public static string MetaDescription(string? summary, string? markdown)
    {
        var source = !string.IsNullOrWhiteSpace(summary)
            ? summary
            : FirstMeaningfulParagraph(markdown);
        return TrimDescription(CleanText(source));
    }

    public static string Keywords(params IEnumerable<string?>[] groups)
    {
        return string.Join(", ", groups
            .SelectMany(group => group)
            .Where(value => !string.IsNullOrWhiteSpace(value))
            .Select(value => value!.Trim())
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .Take(12));
    }

    public static string JsonLd(object value) =>
        JsonSerializer.Serialize(value, new JsonSerializerOptions
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        });

    public static string WebPageJsonLd(
        IConfiguration configuration,
        string schemaType,
        string name,
        string path,
        string? description = null) =>
        JsonLd(new Dictionary<string, object?>
        {
            ["@context"] = "https://schema.org",
            ["@type"] = schemaType,
            ["name"] = name,
            ["url"] = AbsoluteUrl(configuration, path),
            ["description"] = description
        });

    public static string BreadcrumbJsonLd(params (string Name, string Url)[] items) =>
        JsonLd(new Dictionary<string, object?>
        {
            ["@context"] = "https://schema.org",
            ["@type"] = "BreadcrumbList",
            ["itemListElement"] = items.Select((item, index) => new Dictionary<string, object?>
            {
                ["@type"] = "ListItem",
                ["position"] = index + 1,
                ["name"] = item.Name,
                ["item"] = item.Url
            })
        });

    private static string CanonicalUrl(IConfiguration configuration, string value)
    {
        if (!Uri.TryCreate(value, UriKind.Absolute, out var uri))
        {
            return AbsoluteUrl(configuration, value);
        }

        return $"{BaseUrl(configuration)}{NormalisePath(uri.PathAndQuery)}";
    }

    private static string NormalisePath(string? path)
    {
        if (string.IsNullOrWhiteSpace(path) || path == "/")
        {
            return "/";
        }

        return path.StartsWith("/", StringComparison.Ordinal) ? path : $"/{path}";
    }

    private static string FirstMeaningfulParagraph(string? markdown)
    {
        if (string.IsNullOrWhiteSpace(markdown))
        {
            return "Find practical guidance for planning, tracking, and shaping your story in PlotDirector.";
        }

        var withoutCode = CodeFenceRegex().Replace(markdown, " ");
        foreach (var paragraph in ParagraphSplitRegex().Split(withoutCode))
        {
            var cleaned = CleanText(paragraph);
            if (cleaned.Length >= 40)
            {
                return cleaned;
            }
        }

        return CleanText(withoutCode);
    }

    private static string CleanText(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        var cleaned = HtmlTagRegex().Replace(value, " ");
        cleaned = MarkdownImageRegex().Replace(cleaned, "${alt}");
        cleaned = MarkdownLinkRegex().Replace(cleaned, "${text}");
        cleaned = MarkdownSyntaxRegex().Replace(cleaned, " ");
        cleaned = HelperSyntaxRegex().Replace(cleaned, " ");
        cleaned = WhitespaceRegex().Replace(cleaned, " ");
        return System.Net.WebUtility.HtmlDecode(cleaned).Trim();
    }

    private static string TrimDescription(string value)
    {
        var cleaned = CleanText(value);
        if (cleaned.Length <= DescriptionTargetLength)
        {
            return cleaned;
        }

        var cutoff = cleaned.LastIndexOf(' ', DescriptionTargetLength);
        if (cutoff < 120)
        {
            cutoff = DescriptionTargetLength;
        }

        return $"{cleaned[..cutoff].TrimEnd('.', ',', ';', ':', ' ')}.";
    }

    [GeneratedRegex(@"```[\s\S]*?```")]
    private static partial Regex CodeFenceRegex();

    [GeneratedRegex(@"\r?\n\s*\r?\n+")]
    private static partial Regex ParagraphSplitRegex();

    [GeneratedRegex("<[^>]+>")]
    private static partial Regex HtmlTagRegex();

    [GeneratedRegex(@"!\[(?<alt>[^\]]*)\]\([^)]+\)")]
    private static partial Regex MarkdownImageRegex();

    [GeneratedRegex(@"\[(?<text>[^\]]+)\]\([^)]+\)")]
    private static partial Regex MarkdownLinkRegex();

    [GeneratedRegex(@"[#>*_`~\-\|]+")]
    private static partial Regex MarkdownSyntaxRegex();

    [GeneratedRegex(@"<help-icon[^>]*>|@\w+\([^)]*\)")]
    private static partial Regex HelperSyntaxRegex();

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

public sealed record SeoMetadata
{
    public string Title { get; init; } = string.Empty;
    public string Description { get; init; } = string.Empty;
    public string? Keywords { get; init; }
    public string? CanonicalPath { get; init; }
    public string? CanonicalUrl { get; init; }
    public string? OgTitle { get; init; }
    public string? OgDescription { get; init; }
    public string OgType { get; init; } = "website";
    public string? OgImage { get; init; }
    public string TwitterCard { get; init; } = "summary_large_image";
    public IReadOnlyList<string> JsonLd { get; init; } = [];
}
