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

namespace StellaOps.Feedser.Models;

public static class AliasSchemeRegistry
{
    private sealed record AliasScheme(
        string Name,
        Func<string?, bool> Predicate,
        Func<string?, string> Normalizer);

private static readonly AliasScheme[] SchemeDefinitions =
    {
        BuildScheme(AliasSchemes.Cve, alias => alias is not null && Matches(CvERegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "CVE")),
        BuildScheme(AliasSchemes.Ghsa, alias => alias is not null && Matches(GhsaRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "GHSA")),
        BuildScheme(AliasSchemes.OsV, alias => alias is not null && Matches(OsVRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "OSV")),
        BuildScheme(AliasSchemes.Jvn, alias => alias is not null && Matches(JvnRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "JVN")),
        BuildScheme(AliasSchemes.Jvndb, alias => alias is not null && Matches(JvndbRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "JVNDB")),
        BuildScheme(AliasSchemes.Bdu, alias => alias is not null && Matches(BduRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "BDU")),
        BuildScheme(AliasSchemes.Vu, alias => alias is not null && alias.StartsWith("VU#", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "VU", preserveSeparator: '#')),
        BuildScheme(AliasSchemes.Msrc, alias => alias is not null && alias.StartsWith("MSRC-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "MSRC")),
        BuildScheme(AliasSchemes.CiscoSa, alias => alias is not null && alias.StartsWith("CISCO-SA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "CISCO-SA")),
        BuildScheme(AliasSchemes.OracleCpu, alias => alias is not null && alias.StartsWith("ORACLE-CPU", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "ORACLE-CPU")),
        BuildScheme(AliasSchemes.Apsb, alias => alias is not null && alias.StartsWith("APSB-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "APSB")),
        BuildScheme(AliasSchemes.Apa, alias => alias is not null && alias.StartsWith("APA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "APA")),
        BuildScheme(AliasSchemes.AppleHt, alias => alias is not null && alias.StartsWith("APPLE-HT", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "APPLE-HT")),
        BuildScheme(AliasSchemes.ChromiumPost, alias => alias is not null && (alias.StartsWith("CHROMIUM-POST", StringComparison.OrdinalIgnoreCase) || alias.StartsWith("CHROMIUM:", StringComparison.OrdinalIgnoreCase)), NormalizeChromium),
        BuildScheme(AliasSchemes.Vmsa, alias => alias is not null && alias.StartsWith("VMSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "VMSA")),
        BuildScheme(AliasSchemes.Rhsa, alias => alias is not null && alias.StartsWith("RHSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "RHSA")),
        BuildScheme(AliasSchemes.Usn, alias => alias is not null && alias.StartsWith("USN-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "USN")),
        BuildScheme(AliasSchemes.Dsa, alias => alias is not null && alias.StartsWith("DSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "DSA")),
        BuildScheme(AliasSchemes.SuseSu, alias => alias is not null && alias.StartsWith("SUSE-SU-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "SUSE-SU")),
        BuildScheme(AliasSchemes.Icsa, alias => alias is not null && alias.StartsWith("ICSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "ICSA")),
        BuildScheme(AliasSchemes.Cwe, alias => alias is not null && Matches(CweRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "CWE")),
        BuildScheme(AliasSchemes.Cpe, alias => alias is not null && alias.StartsWith("cpe:", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "cpe", uppercase:false)),
        BuildScheme(AliasSchemes.Purl, alias => alias is not null && alias.StartsWith("pkg:", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "pkg", uppercase:false)),
    };

    private static AliasScheme BuildScheme(string name, Func<string?, bool> predicate, Func<string?, string> normalizer)
        => new(
            name,
            predicate,
            alias => normalizer(alias));

    private static readonly ImmutableHashSet<string> SchemeNames = SchemeDefinitions
        .Select(static scheme => scheme.Name)
        .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);

    private static readonly Regex CvERegex = new("^CVE-\\d{4}-\\d{4,}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    private static readonly Regex GhsaRegex = new("^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    private static readonly Regex OsVRegex = new("^OSV-\\d{4}-\\d+$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    private static readonly Regex JvnRegex = new("^JVN-\\d{4}-\\d{6}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    private static readonly Regex JvndbRegex = new("^JVNDB-\\d{4}-\\d{6}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    private static readonly Regex BduRegex = new("^BDU-\\d{4}-\\d+$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    private static readonly Regex CweRegex = new("^CWE-\\d+$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

    public static IReadOnlyCollection<string> KnownSchemes => SchemeNames;

    public static bool IsKnownScheme(string? scheme)
        => !string.IsNullOrWhiteSpace(scheme) && SchemeNames.Contains(scheme);

    public static bool TryGetScheme(string? alias, out string scheme)
    {
        if (string.IsNullOrWhiteSpace(alias))
        {
            scheme = string.Empty;
            return false;
        }

        var candidate = alias.Trim();
        foreach (var entry in SchemeDefinitions)
        {
            if (entry.Predicate(candidate))
            {
                scheme = entry.Name;
                return true;
            }
        }

        scheme = string.Empty;
        return false;
    }

    public static bool TryNormalize(string? alias, out string normalized, out string scheme)
    {
        normalized = string.Empty;
        scheme = string.Empty;

        if (string.IsNullOrWhiteSpace(alias))
        {
            return false;
        }

        var candidate = alias.Trim();
        foreach (var entry in SchemeDefinitions)
        {
            if (entry.Predicate(candidate))
            {
                scheme = entry.Name;
                normalized = entry.Normalizer(candidate);
                return true;
            }
        }

        normalized = candidate;
        return false;
    }

    private static string NormalizePrefix(string? alias, string prefix, bool uppercase = true, char? preserveSeparator = null)
    {
        if (string.IsNullOrWhiteSpace(alias))
        {
            return string.Empty;
        }

        var comparison = StringComparison.OrdinalIgnoreCase;
        if (!alias.StartsWith(prefix, comparison))
        {
            return uppercase ? alias : alias.ToLowerInvariant();
        }

        var remainder = alias[prefix.Length..];
        if (preserveSeparator is { } separator && remainder.Length > 0 && remainder[0] != separator)
        {
            // Edge case: alias is expected to use a specific separator but does not – return unchanged.
            return uppercase ? prefix.ToUpperInvariant() + remainder : prefix + remainder;
        }

        var normalizedPrefix = uppercase ? prefix.ToUpperInvariant() : prefix.ToLowerInvariant();
        return normalizedPrefix + remainder;
    }

    private static string NormalizeChromium(string? alias)
    {
        if (string.IsNullOrWhiteSpace(alias))
        {
            return string.Empty;
        }

        if (alias.StartsWith("CHROMIUM-POST", StringComparison.OrdinalIgnoreCase))
        {
            return NormalizePrefix(alias, "CHROMIUM-POST");
        }

        if (alias.StartsWith("CHROMIUM:", StringComparison.OrdinalIgnoreCase))
        {
            var remainder = alias["CHROMIUM".Length..];
            return "CHROMIUM" + remainder;
        }

        return alias;
    }
    private static bool Matches(Regex? regex, string? candidate)
    {
        if (regex is null || string.IsNullOrWhiteSpace(candidate))
        {
            return false;
        }

        return regex.IsMatch(candidate);
    }
}
