using System;
using System.Threading;
using System.Threading.Tasks;

using Newtonsoft.Json;

using AIRuntime.Runtime.Filesystem;


namespace AIRuntime.Runtime.Capabilities
{
    /// <summary>
    /// Executes capabilities through the middleware pipeline.
    ///
    /// Responsible for:
    /// • JSON deserialization
    /// • Building the execution context
    /// • Running middleware
    /// • Executing the capability
    /// • Serializing the result
    /// </summary>
    public sealed class CapabilityExecutor
    {
        private readonly CapabilityPipeline _pipeline;

        private readonly IFileSystem _fileSystem;


        public CapabilityExecutor(
            CapabilityPipeline pipeline,
            IFileSystem fileSystem)
        {
            _pipeline =
                pipeline ??
                throw new ArgumentNullException(
                    nameof(pipeline));


            _fileSystem =
                fileSystem ??
                throw new ArgumentNullException(
                    nameof(fileSystem));
        }


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

        /// <summary>
        /// Executes a capability without cancellation.
        /// </summary>
        public Task<string> ExecuteAsync(
            ICapability capability,
            string rawArguments)
        {
            return ExecuteAsync(
                capability,
                rawArguments,
                CancellationToken.None);
        }


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

        /// <summary>
        /// Executes a capability with cancellation support.
        /// </summary>
        public async Task<string> ExecuteAsync(
            ICapability capability,
            string rawArguments,
            CancellationToken cancellationToken)
        {
            if (capability == null)
            {
                throw new ArgumentNullException(
                    nameof(capability));
            }


            cancellationToken
                .ThrowIfCancellationRequested();


            //--------------------------------------------------
            // Deserialize
            //--------------------------------------------------

            object arguments;


            if (string.IsNullOrWhiteSpace(
                    rawArguments))
            {
                arguments =
                    Activator.CreateInstance(
                        capability.ArgumentType);
            }
            else
            {
                arguments =
                    JsonConvert.DeserializeObject(
                        rawArguments,
                        capability.ArgumentType);


                if (arguments == null)
                {
                    throw new InvalidOperationException(
                        $"Failed to deserialize arguments for " +
                        $"capability '{capability.Name}'.");
                }
            }


            cancellationToken
                .ThrowIfCancellationRequested();


            //--------------------------------------------------
            // Build Context
            //--------------------------------------------------

            CapabilityContext context =
                new CapabilityContext
                {
                    Capability =
                        capability,

                    RawArguments =
                        rawArguments ??
                        string.Empty,

                    Arguments =
                        arguments,

                    FileSystem =
                        _fileSystem,

                    CancellationToken =
                        cancellationToken
                };


            //--------------------------------------------------
            // Execute Pipeline
            //--------------------------------------------------

            await _pipeline.ExecuteAsync(
                context,
                async currentContext =>
                {
                    cancellationToken
                        .ThrowIfCancellationRequested();


                    object result =
                        await currentContext
                            .Capability
                            .ExecuteObjectAsync(
                                currentContext.Arguments,
                                currentContext);


                    cancellationToken
                        .ThrowIfCancellationRequested();


                    currentContext.Result =
                        SerializeResult(
                            result);
                });


            cancellationToken
                .ThrowIfCancellationRequested();


            return context.Result;
        }


        //--------------------------------------------------
        // Serialization
        //--------------------------------------------------

        private static string SerializeResult(
            object result)
        {
            if (result == null)
                return string.Empty;


            if (result is string text)
                return text;


            return JsonConvert.SerializeObject(
                result,
                Formatting.Indented);
        }
    }
}
