using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Filesystem;
using AIRuntime.Runtime.Providers;
using AIRuntime.Runtime.Providers.Fallback;
using AIRuntime.Runtime.Services.Agent;


namespace AIRuntime.Runtime
{
    /// <summary>
    /// A self-contained runtime module. Packages implement this contract to
    /// contribute services and ordinary runtime configuration without editing
    /// the AIRuntime composition root.
    /// </summary>
    public interface IAIRuntimeModule
    {
        void Configure(
            AIRuntimeBuilder builder);
    }


    /// <summary>
    /// Open composition path for AIRuntime. The existing AIRuntime(string)
    /// constructor remains the default-module shorthand.
    /// </summary>
    public sealed class AIRuntimeBuilder
    {
        private readonly string _workspaceRoot;

        private readonly List<ICapabilityMiddleware> _middleware =
            new List<ICapabilityMiddleware>();

        private readonly List<IAgentObserver> _observers =
            new List<IAgentObserver>();

        private readonly List<Action<AIRuntime>> _runtimeConfiguration =
            new List<Action<AIRuntime>>();

        private IFileSystem _fileSystem;
        private IProviderFailureClassifier _failureClassifier;
        private IProviderToolBuilder _toolBuilder;

        private bool _includeDefaultMiddleware =
            true;

        private bool _includeConsoleObserver =
            true;

        private int _built;


        public AIRuntimeBuilder(
            string workspaceRoot)
        {
            if (string.IsNullOrWhiteSpace(workspaceRoot))
            {
                throw new ArgumentException(
                    "Workspace path cannot be null or empty.",
                    nameof(workspaceRoot));
            }

            _workspaceRoot =
                workspaceRoot;
        }


        public AIRuntimeBuilder UseFileSystem(
            IFileSystem fileSystem)
        {
            ThrowIfBuilt();

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

            return this;
        }


        public AIRuntimeBuilder UseMiddleware(
            ICapabilityMiddleware middleware)
        {
            ThrowIfBuilt();

            _middleware.Add(
                middleware ??
                throw new ArgumentNullException(
                    nameof(middleware)));

            return this;
        }


        public AIRuntimeBuilder WithoutDefaultMiddleware()
        {
            ThrowIfBuilt();

            _includeDefaultMiddleware =
                false;

            return this;
        }


        public AIRuntimeBuilder AddObserver(
            IAgentObserver observer)
        {
            ThrowIfBuilt();

            _observers.Add(
                observer ??
                throw new ArgumentNullException(
                    nameof(observer)));

            return this;
        }


        public AIRuntimeBuilder WithoutConsoleObserver()
        {
            ThrowIfBuilt();

            _includeConsoleObserver =
                false;

            return this;
        }


        public AIRuntimeBuilder UseFailureClassifier(
            IProviderFailureClassifier failureClassifier)
        {
            ThrowIfBuilt();

            _failureClassifier =
                failureClassifier ??
                throw new ArgumentNullException(
                    nameof(failureClassifier));

            return this;
        }


        public AIRuntimeBuilder UseProviderToolBuilder(
            IProviderToolBuilder toolBuilder)
        {
            ThrowIfBuilt();

            _toolBuilder =
                toolBuilder ??
                throw new ArgumentNullException(
                    nameof(toolBuilder));

            return this;
        }


        public AIRuntimeBuilder ConfigureRuntime(
            Action<AIRuntime> configure)
        {
            ThrowIfBuilt();

            _runtimeConfiguration.Add(
                configure ??
                throw new ArgumentNullException(
                    nameof(configure)));

            return this;
        }


        public AIRuntimeBuilder UseProvider(
            IProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException(
                    nameof(provider));
            }

            return ConfigureRuntime(
                runtime =>
                    runtime.UseProvider(
                        provider));
        }


        public AIRuntimeBuilder RegisterCapability(
            ICapability capability)
        {
            if (capability == null)
            {
                throw new ArgumentNullException(
                    nameof(capability));
            }

            return ConfigureRuntime(
                runtime =>
                    runtime.RegisterCapability(
                        capability));
        }


        public AIRuntimeBuilder AddModule(
            IAIRuntimeModule module)
        {
            ThrowIfBuilt();

            (module ??
             throw new ArgumentNullException(
                 nameof(module)))
                .Configure(
                    this);

            return this;
        }


        public AIRuntime Build()
        {
            if (Interlocked.Exchange(
                    ref _built,
                    1) != 0)
            {
                throw new InvalidOperationException(
                    "An AIRuntimeBuilder can build only one runtime instance.");
            }

            AIRuntime runtime =
                new AIRuntime(
                    _workspaceRoot,
                    new AIRuntimeCompositionOptions
                    {
                        FileSystem = _fileSystem,
                        Middleware = _middleware.ToArray(),
                        IncludeDefaultMiddleware = _includeDefaultMiddleware,
                        Observers = _observers.ToArray(),
                        IncludeConsoleObserver = _includeConsoleObserver,
                        FailureClassifier = _failureClassifier,
                        ToolBuilder = _toolBuilder
                    });

            foreach (Action<AIRuntime> configure in _runtimeConfiguration)
            {
                configure(runtime);
            }

            return runtime;
        }


        private void ThrowIfBuilt()
        {
            if (Volatile.Read(
                    ref _built) != 0)
            {
                throw new InvalidOperationException(
                    "The AIRuntimeBuilder cannot be changed after Build().");
            }
        }
    }


    internal sealed class AIRuntimeCompositionOptions
    {
        public IFileSystem FileSystem { get; set; }

        public IReadOnlyList<ICapabilityMiddleware> Middleware { get; set; }

        public bool IncludeDefaultMiddleware { get; set; }

        public IReadOnlyList<IAgentObserver> Observers { get; set; }

        public bool IncludeConsoleObserver { get; set; }

        public IProviderFailureClassifier FailureClassifier { get; set; }

        public IProviderToolBuilder ToolBuilder { get; set; }
    }
}
