using NexusShell.Models;

namespace NexusShell.Services.Policy;

public sealed class NexusPolicyEvaluator
{
    public bool IsAppAllowed(NexusPolicy policy, string appKey, NexusRole role, NexusTrustStatus trust) =>
        EvaluateTarget(policy, Normalize(appKey), role, trust, isApp: true);

    public bool IsFeatureAllowed(NexusPolicy policy, string featureKey, NexusRole role, NexusTrustStatus trust) =>
        EvaluateTarget(policy, Normalize(featureKey), role, trust, isApp: false);

    public bool IsCommandAllowed(NexusPolicy policy, string command, NexusRole role, NexusTrustStatus trust)
    {
        var key = Normalize(command);
        if (IsDeniedByRules(policy, key, role, trust, isApp: false, isCommand: true))
        {
            return false;
        }

        if (policy.DenyCommands.Any(d => Matches(d, key)))
        {
            return false;
        }

        if (policy.AllowCommands.Count > 0)
        {
            return policy.AllowCommands.Any(a => Matches(a, key));
        }

        return true;
    }

    public bool IsSettingsSectionAllowed(NexusPolicy policy, string sectionName) =>
        !policy.DenySettingsSections.Any(d => Matches(d, Normalize(sectionName)));

    public bool CanAccessConsole(NexusPolicy policy, NexusRole role, NexusTrustStatus trust) =>
        IsFeatureAllowed(policy, "console", role, trust);

    public bool CanAccessStore(NexusPolicy policy, NexusRole role, NexusTrustStatus trust) =>
        IsFeatureAllowed(policy, "store", role, trust) && IsAppAllowed(policy, "store", role, trust);

    public NexusRole? GetRequiredRole(NexusPolicy policy, string target)
    {
        var normalizedTarget = Normalize(target);
        foreach (var rule in policy.Rules)
        {
            if (rule.ParsedType != NexusPolicyRuleType.RequireRole)
            {
                continue;
            }

            if (!Matches(rule.Target, normalizedTarget))
            {
                continue;
            }

            if (Enum.TryParse<NexusRole>(rule.Value, ignoreCase: true, out var required))
            {
                return required;
            }
        }

        return null;
    }

    public static bool RoleMeetsMinimum(NexusRole actual, NexusRole minimum) =>
        (int)actual >= (int)minimum;

    public static NexusRole ParseRole(string? roleName) =>
        Enum.TryParse<NexusRole>(roleName, ignoreCase: true, out var parsed)
            ? parsed
            : NexusRole.User;

    public static bool MeetsTrustRequirement(NexusTrustStatus actual, string? requiredValue)
    {
        if (string.IsNullOrWhiteSpace(requiredValue))
        {
            return actual == NexusTrustStatus.Trusted;
        }

        return Enum.TryParse<NexusTrustStatus>(requiredValue, ignoreCase: true, out var required)
            && actual == required;
    }

    private bool EvaluateTarget(
        NexusPolicy policy,
        string key,
        NexusRole role,
        NexusTrustStatus trust,
        bool isApp)
    {
        if (IsDeniedByRules(policy, key, role, trust, isApp, isCommand: false))
        {
            return false;
        }

        var denyList = isApp ? policy.DenyApps : policy.DenyFeatures;
        var allowList = isApp ? policy.AllowApps : policy.AllowFeatures;

        if (denyList.Any(d => Matches(d, key)))
        {
            return false;
        }

        if (allowList.Count > 0)
        {
            return allowList.Any(a => Matches(a, key));
        }

        return true;
    }

    private bool IsDeniedByRules(
        NexusPolicy policy,
        string key,
        NexusRole role,
        NexusTrustStatus trust,
        bool isApp,
        bool isCommand)
    {
        foreach (var rule in policy.Rules)
        {
            if (!Matches(rule.Target, key))
            {
                continue;
            }

            switch (rule.ParsedType)
            {
                case NexusPolicyRuleType.DenyApp when isApp:
                    return true;
                case NexusPolicyRuleType.DenyFeature when !isApp && !isCommand:
                    return true;
                case NexusPolicyRuleType.AllowApp when isApp:
                    break;
                case NexusPolicyRuleType.AllowFeature when !isApp && !isCommand:
                    break;
                case NexusPolicyRuleType.RequireRole:
                    var requiredRole = GetRequiredRoleForTarget(policy, key);
                    if (requiredRole is not null && !RoleMeetsMinimum(role, requiredRole.Value))
                    {
                        return true;
                    }

                    break;
                case NexusPolicyRuleType.RequireTrust:
                    if (!MeetsTrustRequirement(trust, rule.Value))
                    {
                        return true;
                    }

                    break;
            }
        }

        return false;
    }

    private NexusRole? GetRequiredRoleForTarget(NexusPolicy policy, string normalizedTarget)
    {
        foreach (var rule in policy.Rules)
        {
            if (rule.ParsedType != NexusPolicyRuleType.RequireRole)
            {
                continue;
            }

            if (!Matches(rule.Target, normalizedTarget))
            {
                continue;
            }

            if (Enum.TryParse<NexusRole>(rule.Value, ignoreCase: true, out var required))
            {
                return required;
            }
        }

        return null;
    }

    private static string Normalize(string value) => value.Trim().ToLowerInvariant();

    private static bool Matches(string ruleValue, string key) =>
        string.Equals(Normalize(ruleValue), key, StringComparison.Ordinal);
}
