using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

using AIRuntime.Runtime.Capabilities.Permissions;
using AIRuntime.Runtime.Filesystem;
using AIRuntime.Runtime.Models;


namespace AIRuntime.Runtime.Capabilities
{
    /// <summary>
    /// Looks up capabilities, validates permissions,
    /// requests approval when required, and executes
    /// capabilities through the CapabilityExecutor pipeline.
    /// </summary>
    public sealed class CapabilityDispatcher
    {
        private readonly CapabilityRegistry _registry;

        private readonly CapabilityExecutor _executor;

        private readonly CapabilityPermissionPolicy _permissionPolicy;


        private ICapabilityApprovalHandler _approvalHandler;


        public CapabilityDispatcher(
            CapabilityRegistry registry,
            IFileSystem fileSystem,
            CapabilityPermissionPolicy permissionPolicy)
            : this(
                registry,
                fileSystem,
                permissionPolicy,
                middleware: null,
                includeDefaultMiddleware: true)
        {
        }


        public CapabilityDispatcher(
            CapabilityRegistry registry,
            IFileSystem fileSystem,
            CapabilityPermissionPolicy permissionPolicy,
            IEnumerable<ICapabilityMiddleware> middleware,
            bool includeDefaultMiddleware = true)
        {
            if (registry == null)
            {
                throw new ArgumentNullException(
                    nameof(registry));
            }


            if (fileSystem == null)
            {
                throw new ArgumentNullException(
                    nameof(fileSystem));
            }


            if (permissionPolicy == null)
            {
                throw new ArgumentNullException(
                    nameof(permissionPolicy));
            }


            _registry =
                registry;


            _permissionPolicy =
                permissionPolicy;


            //--------------------------------------------------
            // Build Default Pipeline
            //--------------------------------------------------

            CapabilityPipeline pipeline =
                new CapabilityPipeline();

            if (includeDefaultMiddleware)
            {
                pipeline.Use(
                    new ExceptionMiddleware());

                pipeline.Use(
                    new LoggingMiddleware());

                pipeline.Use(
                    new TimingMiddleware());

                pipeline.Use(
                    new ValidationMiddleware());
            }

            if (middleware != null)
            {
                foreach (ICapabilityMiddleware component in middleware)
                {
                    pipeline.Use(
                        component ??
                        throw new ArgumentException(
                            "Capability middleware cannot contain null entries.",
                            nameof(middleware)));
                }
            }


            _executor =
                new CapabilityExecutor(
                    pipeline,
                    fileSystem);
        }


        //--------------------------------------------------
        // Approval Handler
        //--------------------------------------------------

        /// <summary>
        /// Sets the handler used when a capability requires approval.
        ///
        /// A null handler means approval-required capabilities
        /// cannot execute.
        /// </summary>
        public void SetApprovalHandler(
            ICapabilityApprovalHandler approvalHandler)
        {
            _approvalHandler =
                approvalHandler;
        }


        //--------------------------------------------------
        // Execution Without Cancellation
        //--------------------------------------------------

        public Task<string> DispatchAsync(
            ToolCall toolCall)
        {
            return DispatchAsync(
                toolCall,
                sessionId: string.Empty,
                CancellationToken.None);
        }


        //--------------------------------------------------
        // Cancellation-Aware Execution
        //--------------------------------------------------

        public Task<string> DispatchAsync(
            ToolCall toolCall,
            CancellationToken cancellationToken)
        {
            return DispatchAsync(
                toolCall,
                sessionId: string.Empty,
                cancellationToken);
        }


        //--------------------------------------------------
        // Session-Aware Execution
        //--------------------------------------------------

        public async Task<string> DispatchAsync(
            ToolCall toolCall,
            string sessionId,
            CancellationToken cancellationToken)
        {
            if (toolCall == null)
            {
                throw new ArgumentNullException(
                    nameof(toolCall));
            }


            cancellationToken
                .ThrowIfCancellationRequested();


            if (string.IsNullOrWhiteSpace(
                    toolCall.Name))
            {
                throw new ArgumentException(
                    "Tool call capability name cannot be empty.",
                    nameof(toolCall));
            }


            if (!_registry.Contains(
                    toolCall.Name))
            {
                throw new InvalidOperationException(
                    $"Capability '{toolCall.Name}' is not registered.");
            }


            ICapability capability =
                _registry.Get(
                    toolCall.Name);


            cancellationToken
                .ThrowIfCancellationRequested();


            //--------------------------------------------------
            // Permission Enforcement
            //--------------------------------------------------

            await EnsurePermissionAsync(
                capability,
                toolCall.Arguments,
                sessionId,
                cancellationToken);


            cancellationToken
                .ThrowIfCancellationRequested();


            //--------------------------------------------------
            // Capability Execution
            //--------------------------------------------------

            return await _executor.ExecuteAsync(
                capability,
                toolCall.Arguments,
                cancellationToken);
        }


        //--------------------------------------------------
        // Permission Evaluation
        //--------------------------------------------------

        private async Task EnsurePermissionAsync(
            ICapability capability,
            string arguments,
            string sessionId,
            CancellationToken cancellationToken)
        {
            IReadOnlyList<CapabilityPermission> requiredPermissions =
                ResolveRequiredPermissions(capability);

            foreach (CapabilityPermission requiredPermission in requiredPermissions)
            {
                await EnsurePermissionAsync(
                    capability,
                    requiredPermission,
                    arguments,
                    sessionId,
                    cancellationToken);
            }
        }


        private async Task EnsurePermissionAsync(
            ICapability capability,
            CapabilityPermission requiredPermission,
            string arguments,
            string sessionId,
            CancellationToken cancellationToken)
        {
            CapabilityPermissionDecision decision =
                _permissionPolicy.GetDecision(requiredPermission);


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


                case CapabilityPermissionDecision.Deny:
                    throw CapabilityPermissionException
                        .CreateDenied(
                            capability.Name,
                            requiredPermission);


                case CapabilityPermissionDecision.RequireApproval:
                    await RequestApprovalAsync(
                        capability,
                        requiredPermission,
                        arguments,
                        sessionId,
                        cancellationToken);

                    return;


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


        //--------------------------------------------------
        // Approval Request
        //--------------------------------------------------

        private async Task RequestApprovalAsync(
            ICapability capability,
            CapabilityPermission requiredPermission,
            string arguments,
            string sessionId,
            CancellationToken cancellationToken)
        {
            if (_approvalHandler == null)
            {
                throw CapabilityPermissionException
                    .CreateApprovalRequired(
                        capability.Name,
                        requiredPermission);
            }


            CapabilityApprovalRequest request =
                new CapabilityApprovalRequest(
                    capability.Name,
                    requiredPermission,
                    arguments,
                    sessionId);


            CapabilityApprovalResult result =
                await _approvalHandler.RequestApprovalAsync(
                    request,
                    cancellationToken);


            cancellationToken
                .ThrowIfCancellationRequested();


            if (result == null)
            {
                throw CapabilityPermissionException
                    .CreateRejected(
                            capability.Name,
                            requiredPermission,
                        "The approval handler returned no result.");
            }


            if (!result.Approved)
            {
                throw CapabilityPermissionException
                    .CreateRejected(
                            capability.Name,
                            requiredPermission,
                            result.Reason);
            }
        }


        private static IReadOnlyList<CapabilityPermission>
            ResolveRequiredPermissions(ICapability capability)
        {
            List<CapabilityPermission> permissions =
                new List<CapabilityPermission> { capability.Permission };

            if (capability is ICapabilityAuthorizationSemantics semantics &&
                semantics.RequiredPermissions != null)
            {
                foreach (CapabilityPermission permission in semantics.RequiredPermissions)
                {
                    if (!permissions.Contains(permission))
                        permissions.Add(permission);
                }
            }

            return permissions;
        }
    }
}
