using System;
using System.Collections.Generic;


namespace AIRuntime.Runtime.Capabilities.Permissions
{
    /// <summary>
    /// Stores permission decisions used when capabilities
    /// are requested by an agent.
    /// </summary>
    public sealed class CapabilityPermissionPolicy
    {
        private readonly Dictionary<
            CapabilityPermission,
            CapabilityPermissionDecision> _decisions;


        public CapabilityPermissionPolicy()
        {
            _decisions =
                new Dictionary<
                    CapabilityPermission,
                    CapabilityPermissionDecision>();


            ResetToDefaults();
        }


        /// <summary>
        /// Restores the default permission policy.
        ///
        /// Read-only capabilities are allowed.
        /// All other permissions require approval.
        /// </summary>
        public void ResetToDefaults()
        {
            _decisions.Clear();


            _decisions[CapabilityPermission.ReadOnly] =
                CapabilityPermissionDecision.Allow;


            _decisions[CapabilityPermission.Write] =
                CapabilityPermissionDecision.RequireApproval;


            _decisions[CapabilityPermission.Destructive] =
                CapabilityPermissionDecision.RequireApproval;


            _decisions[CapabilityPermission.Process] =
                CapabilityPermissionDecision.RequireApproval;


            _decisions[CapabilityPermission.Network] =
                CapabilityPermissionDecision.RequireApproval;
        }


        /// <summary>
        /// Returns the current decision for a permission.
        /// </summary>
        public CapabilityPermissionDecision GetDecision(
            CapabilityPermission permission)
        {
            if (_decisions.TryGetValue(
                    permission,
                    out CapabilityPermissionDecision decision))
            {
                return decision;
            }


            return CapabilityPermissionDecision.Deny;
        }


        /// <summary>
        /// Sets the decision associated with a permission.
        /// </summary>
        public void SetDecision(
            CapabilityPermission permission,
            CapabilityPermissionDecision decision)
        {
            _decisions[permission] =
                decision;
        }


        public void Allow(
            CapabilityPermission permission)
        {
            SetDecision(
                permission,
                CapabilityPermissionDecision.Allow);
        }


        public void Deny(
            CapabilityPermission permission)
        {
            SetDecision(
                permission,
                CapabilityPermissionDecision.Deny);
        }


        public void RequireApproval(
            CapabilityPermission permission)
        {
            SetDecision(
                permission,
                CapabilityPermissionDecision.RequireApproval);
        }


        public bool IsAllowed(
            CapabilityPermission permission)
        {
            return GetDecision(permission) ==
                   CapabilityPermissionDecision.Allow;
        }


        public bool IsDenied(
            CapabilityPermission permission)
        {
            return GetDecision(permission) ==
                   CapabilityPermissionDecision.Deny;
        }


        public bool RequiresApproval(
            CapabilityPermission permission)
        {
            return GetDecision(permission) ==
                   CapabilityPermissionDecision.RequireApproval;
        }


        /// <summary>
        /// Throws when the current policy does not allow
        /// the requested capability to execute automatically.
        /// </summary>
        public void DemandPermission(
            string capabilityName,
            CapabilityPermission permission)
        {
            if (string.IsNullOrWhiteSpace(capabilityName))
            {
                throw new ArgumentException(
                    "Capability name cannot be empty.",
                    nameof(capabilityName));
            }


            CapabilityPermissionDecision decision =
                GetDecision(
                    permission);


            switch (decision)
            {
                case CapabilityPermissionDecision.Allow:
                    return;

                case CapabilityPermissionDecision.Deny:
                    throw CapabilityPermissionException
                        .CreateDenied(
                            capabilityName,
                            permission);

                case CapabilityPermissionDecision.RequireApproval:
                    throw CapabilityPermissionException
                        .CreateApprovalRequired(
                            capabilityName,
                            permission);

                default:
                    throw new ArgumentOutOfRangeException(
                        nameof(decision),
                        decision,
                        "Unknown capability permission decision.");
            }
        }
    }
}