using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace StellaOps.Feedser.Models;

/// <summary>
/// Central registry of allowed affected-package status labels to keep connectors consistent.
/// </summary>
public static class AffectedPackageStatusCatalog
{
    public const string KnownAffected = "known_affected";
    public const string KnownNotAffected = "known_not_affected";
    public const string UnderInvestigation = "under_investigation";
    public const string Fixed = "fixed";
    public const string FirstFixed = "first_fixed";
    public const string Mitigated = "mitigated";
    public const string NotApplicable = "not_applicable";
    public const string Affected = "affected";
    public const string NotAffected = "not_affected";
    public const string Pending = "pending";
    public const string Unknown = "unknown";

    private static readonly string[] CanonicalStatuses =
    {
        KnownAffected,
        KnownNotAffected,
        UnderInvestigation,
        Fixed,
        FirstFixed,
        Mitigated,
        NotApplicable,
        Affected,
        NotAffected,
        Pending,
        Unknown,
    };

    private static readonly IReadOnlyList<string> AllowedStatuses = Array.AsReadOnly(CanonicalStatuses);

    private static readonly IReadOnlyDictionary<string, string> StatusMap = BuildStatusMap();

    public static IReadOnlyList<string> Allowed => AllowedStatuses;

    public static string Normalize(string status)
    {
        if (!TryNormalize(status, out var normalized))
        {
            throw new ArgumentOutOfRangeException(nameof(status), status, "Status is not part of the allowed affected-package status glossary.");
        }

        return normalized;
    }

    public static bool TryNormalize(string? status, [NotNullWhen(true)] out string? normalized)
    {
        normalized = null;

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

        var token = Sanitize(status);
        if (token.Length == 0)
        {
            return false;
        }

        if (!StatusMap.TryGetValue(token, out normalized))
        {
            return false;
        }

        return true;
    }

    public static bool IsAllowed(string? status)
        => TryNormalize(status, out _);

    private static IReadOnlyDictionary<string, string> BuildStatusMap()
    {
        var map = new Dictionary<string, string>(StringComparer.Ordinal);
        foreach (var status in CanonicalStatuses)
        {
            map[Sanitize(status)] = status;
        }

        Add(map, "known not vulnerable", KnownNotAffected);
        Add(map, "known unaffected", KnownNotAffected);
        Add(map, "known not impacted", KnownNotAffected);
        Add(map, "vulnerable", Affected);
        Add(map, "impacted", Affected);
        Add(map, "impacting", Affected);
        Add(map, "not vulnerable", NotAffected);
        Add(map, "unaffected", NotAffected);
        Add(map, "not impacted", NotAffected);
        Add(map, "no impact", NotAffected);
        Add(map, "impact free", NotAffected);
        Add(map, "investigating", UnderInvestigation);
        Add(map, "analysis in progress", UnderInvestigation);
        Add(map, "analysis pending", UnderInvestigation);
        Add(map, "open", UnderInvestigation);
        Add(map, "patch available", Fixed);
        Add(map, "fix available", Fixed);
        Add(map, "patched", Fixed);
        Add(map, "resolved", Fixed);
        Add(map, "remediated", Fixed);
        Add(map, "workaround available", Mitigated);
        Add(map, "mitigation available", Mitigated);
        Add(map, "mitigation provided", Mitigated);
        Add(map, "not applicable", NotApplicable);
        Add(map, "n/a", NotApplicable);
        Add(map, "na", NotApplicable);
        Add(map, "does not apply", NotApplicable);
        Add(map, "out of scope", NotApplicable);
        Add(map, "pending fix", Pending);
        Add(map, "awaiting fix", Pending);
        Add(map, "awaiting patch", Pending);
        Add(map, "scheduled", Pending);
        Add(map, "planned", Pending);
        Add(map, "tbd", Unknown);
        Add(map, "to be determined", Unknown);
        Add(map, "undetermined", Unknown);
        Add(map, "not yet known", Unknown);

        return map;
    }

    private static void Add(IDictionary<string, string> map, string alias, string canonical)
    {
        var key = Sanitize(alias);
        if (key.Length == 0)
        {
            return;
        }

        map[key] = canonical;
    }

    private static string Sanitize(string value)
    {
        var span = value.AsSpan();
        var buffer = new char[span.Length];
        var index = 0;

        foreach (var ch in span)
        {
            if (char.IsLetterOrDigit(ch))
            {
                buffer[index++] = char.ToLowerInvariant(ch);
            }
        }

        return index == 0 ? string.Empty : new string(buffer, 0, index);
    }
}
