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

using AIRuntime.Runtime.Services.Agent;

using RuntimeFacade =
    AIRuntime.Runtime.AIRuntime;


namespace AIRuntime.Editor
{
    [UnityEditor.InitializeOnLoad]
    internal static class AIRuntimeProviderJournal
    {
        static AIRuntimeProviderJournal()
        {
            AIRuntime.Runtime.Providers.ProviderTransportDebug.ConfigureFileLogging(
                System.IO.Path.Combine(System.IO.Directory.GetParent(UnityEngine.Application.dataPath).FullName,
                    "Logs", "ProviderDiagnostics"));
        }
    }

    /// <summary>
    /// Editor-side runtime extension. Integrations register implementations
    /// explicitly, then attach the registry before starting a runtime run.
    /// </summary>
    public interface IAIRuntimeEditorRuntimePlugin
    {
        string Id
        {
            get;
        }


        IDisposable Attach(
            RuntimeFacade runtime,
            AIRuntimeEditorRuntimePluginContext context);
    }


    /// <summary>
    /// Maps structured runtime lifecycle events and explicit contextual
    /// reports into the safe activity sink.
    /// </summary>
    public sealed class AIRuntimeAgentActivityObserver :
        IAgentObserver,
        IDisposable
    {
        private readonly IAIRuntimeAgentActivitySink _sink;
        private readonly AIRuntimeEditorRuntimePluginContext _context;

        private int _disposed;


        public AIRuntimeAgentActivityObserver(
            IAIRuntimeAgentActivitySink sink,
            AIRuntimeEditorRuntimePluginContext context)
        {
            _sink =
                sink ??
                throw new ArgumentNullException(
                    nameof(sink));

            _context =
                context ??
                throw new ArgumentNullException(
                    nameof(context));

            _context.ActivityReported +=
                OnContextActivityReported;
        }


        public void OnAgentEvent(
            AgentEvent agentEvent)
        {
            if (agentEvent ==
                    null ||
                Volatile.Read(
                    ref _disposed) !=
                0)
            {
                return;
            }

            AIRuntime.Runtime.Providers.ProviderTransportDebug.WriteEvent("Harness", "AGENT " + agentEvent.Type,
                new {
                    _context.CorrelationId, _context.Source, _context.Agent, _context.Objective, _context.Stage,
                    agentEvent.Timestamp, agentEvent.ToolName, agentEvent.Message
                }, agentEvent.SessionId);

            AIRuntimeAgentActivityKind kind;
            string summary;
            string detail =
                string.Empty;

            switch (agentEvent.Type)
            {
                case AgentEventType.RunStarted:
                    kind =
                        AIRuntimeAgentActivityKind.RunStarted;
                    summary =
                        "Run started.";
                    break;

                case AgentEventType.ToolStarted:
                    kind =
                        AIRuntimeAgentActivityKind.ToolStarted;
                    summary =
                        string.IsNullOrWhiteSpace(
                            agentEvent.ToolName)
                            ? "Calling a capability."
                            : $"Calling {agentEvent.ToolName}.";
                    detail =
                        AIRuntimeAgentActivityStore
                            .SummarizeToolArguments(
                                agentEvent.Message);
                    break;

                case AgentEventType.ToolCompleted:
                    kind =
                        AIRuntimeAgentActivityKind.ToolCompleted;
                    summary =
                        string.IsNullOrWhiteSpace(
                            agentEvent.ToolName)
                            ? "Capability completed."
                            : $"{agentEvent.ToolName} completed.";
                    detail =
                        AIRuntimeAgentActivityStore
                            .SummarizeToolResult(
                                agentEvent.Message);
                    break;

                case AgentEventType.ToolFailed:
                    kind =
                        AIRuntimeAgentActivityKind.ToolFailed;
                    summary =
                        string.IsNullOrWhiteSpace(
                            agentEvent.ToolName)
                            ? "Capability failed."
                            : $"{agentEvent.ToolName} failed.";
                    detail =
                        agentEvent.Message ??
                        string.Empty;
                    break;

                case AgentEventType.ModelResponseRepair:
                    kind =
                        AIRuntimeAgentActivityKind.Retry;
                    summary =
                        "Correcting a malformed model capability call.";
                    detail =
                        agentEvent.Message ??
                        string.Empty;
                    break;

                case AgentEventType.ModelResponseNormalized:
                    kind =
                        AIRuntimeAgentActivityKind.Status;
                    summary =
                        "Normalized a provider capability-call batch.";
                    detail =
                        agentEvent.Message ??
                        string.Empty;
                    break;

                case AgentEventType.RunCompleted:
                    kind =
                        AIRuntimeAgentActivityKind.RunCompleted;
                    summary =
                        "Run completed.";
                    break;

                case AgentEventType.RunCancelled:
                    kind =
                        AIRuntimeAgentActivityKind.RunCancelled;
                    summary =
                        "Run cancelled.";
                    break;

                case AgentEventType.RunTimedOut:
                    kind =
                        AIRuntimeAgentActivityKind.RunTimedOut;
                    summary =
                        "Run timed out.";
                    detail =
                        agentEvent.Message ??
                        string.Empty;
                    break;

                case AgentEventType.RunFailed:
                    kind =
                        AIRuntimeAgentActivityKind.RunFailed;
                    summary =
                        "Run failed.";
                    detail =
                        agentEvent.Message ??
                        string.Empty;
                    break;

                default:
                    return;
            }

            try
            {
                _sink.Publish(
                    _context,
                    kind,
                    summary,
                    detail,
                    agentEvent.SessionId,
                    agentEvent.ToolName,
                    agentEvent.Timestamp);
            }
            catch
            {
                // Observability must remain outside the execution contract.
            }
        }


        public void Dispose()
        {
            if (Interlocked.Exchange(
                    ref _disposed,
                    1) !=
                0)
            {
                return;
            }

            _context.ActivityReported -=
                OnContextActivityReported;
        }


        private void OnContextActivityReported(
            AIRuntimeEditorRuntimePluginContext context,
            AIRuntimeEditorRuntimeActivityReport report)
        {
            if (report ==
                    null ||
                Volatile.Read(
                    ref _disposed) !=
                0)
            {
                return;
            }

            try
            {
                AIRuntime.Runtime.Providers.ProviderTransportDebug.WriteEvent("Harness", "ACTIVITY " + report.Kind,
                    new {
                        context.CorrelationId, context.Source, context.Agent, context.Objective, context.Stage,
                        report.Summary, report.Detail
                    });
                _sink.Publish(
                    context,
                    report.Kind,
                    report.Summary,
                    report.Detail,
                    timestampUtc:
                        report.TimestampUtc);
            }
            catch
            {
                // A store supplied by a plugin must not affect the run.
            }
        }
    }


    /// <summary>
    /// Built-in observer plugin used by the Agent Activity window.
    /// </summary>
    public sealed class AIRuntimeAgentActivityRuntimePlugin :
        IAIRuntimeEditorRuntimePlugin
    {
        public const string PluginId =
            "airuntime.editor.agent-activity";

        private readonly IAIRuntimeAgentActivitySink _sink;


        public AIRuntimeAgentActivityRuntimePlugin(
            IAIRuntimeAgentActivitySink sink)
        {
            _sink =
                sink ??
                throw new ArgumentNullException(
                    nameof(sink));
        }


        public string Id =>
            PluginId;


        public IDisposable Attach(
            RuntimeFacade runtime,
            AIRuntimeEditorRuntimePluginContext context)
        {
            if (runtime ==
                null)
            {
                throw new ArgumentNullException(
                    nameof(runtime));
            }

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

            AIRuntimeAgentActivityObserver observer =
                new AIRuntimeAgentActivityObserver(
                    _sink,
                    context);

            try
            {
                runtime.AddAgentObserver(
                    observer);
            }
            catch
            {
                observer.Dispose();
                throw;
            }

            return new RuntimeObserverAttachment(
                runtime,
                observer);
        }


        private sealed class RuntimeObserverAttachment :
            IDisposable
        {
            private RuntimeFacade _runtime;
            private AIRuntimeAgentActivityObserver _observer;


            public RuntimeObserverAttachment(
                RuntimeFacade runtime,
                AIRuntimeAgentActivityObserver observer)
            {
                _runtime = runtime;
                _observer = observer;
            }


            public void Dispose()
            {
                AIRuntimeAgentActivityObserver observer =
                    Interlocked.Exchange(
                        ref _observer,
                        null);

                RuntimeFacade runtime =
                    Interlocked.Exchange(
                        ref _runtime,
                        null);

                if (observer ==
                    null)
                {
                    return;
                }

                observer.Dispose();

                if (runtime ==
                    null)
                {
                    return;
                }

                try
                {
                    runtime.RemoveAgentObserver(
                        observer);
                }
                catch
                {
                    // Disposal during a still-active runtime is best effort.
                    // The disposed observer ignores any later callbacks.
                }
            }
        }
    }


    /// <summary>
    /// Explicit registry for Editor integrations that extend a runtime with
    /// observers or other run-scoped behavior. Plugin failures are isolated.
    /// </summary>
    public static class AIRuntimeEditorRuntimePluginRegistry
    {
        private sealed class RegisteredRuntimePlugin :
            IAIRuntimeEditorRuntimePlugin
        {
            private readonly IAIRuntimeEditorRuntimePlugin _inner;


            public RegisteredRuntimePlugin(
                string id,
                IAIRuntimeEditorRuntimePlugin inner)
            {
                Id = id;
                _inner = inner;
            }


            public string Id { get; }


            public IDisposable Attach(
                RuntimeFacade runtime,
                AIRuntimeEditorRuntimePluginContext context)
            {
                return _inner.Attach(
                    runtime,
                    context);
            }
        }


        private static readonly object Gate =
            new object();

        private static readonly Dictionary<
            string,
            IAIRuntimeEditorRuntimePlugin> Plugins =
            new Dictionary<
                string,
                IAIRuntimeEditorRuntimePlugin>(
                StringComparer.Ordinal);

        private static int _revision =
            1;


        static AIRuntimeEditorRuntimePluginRegistry()
        {
            IAIRuntimeEditorRuntimePlugin builtIn =
                new AIRuntimeAgentActivityRuntimePlugin(
                    AIRuntimeAgentActivityStore.Shared);

            Plugins.Add(
                builtIn.Id,
                builtIn);
        }


        /// <summary>
        /// Changes whenever the runtime plugin set changes. Hosts include this
        /// value in their composition signature so late plugin registration is
        /// applied before the next run without requiring an editor reload.
        /// </summary>
        public static int Revision
        {
            get
            {
                lock (Gate)
                {
                    return _revision;
                }
            }
        }


        public static IDisposable Register(
            IAIRuntimeEditorRuntimePlugin plugin)
        {
            if (plugin ==
                null)
            {
                throw new ArgumentNullException(
                    nameof(plugin));
            }

            string pluginId =
                NormalizePluginId(
                    plugin.Id);

            RegisteredRuntimePlugin registeredPlugin =
                new RegisteredRuntimePlugin(
                    pluginId,
                    plugin);

            lock (Gate)
            {
                if (Plugins.ContainsKey(
                        pluginId))
                {
                    throw new InvalidOperationException(
                        $"An AIRuntime Editor runtime plugin with ID '{pluginId}' is already registered.");
                }

                Plugins.Add(
                    pluginId,
                    registeredPlugin);

                _revision++;
            }

            return new PluginRegistration(
                pluginId,
                registeredPlugin);
        }


        public static IReadOnlyList<IAIRuntimeEditorRuntimePlugin>
            GetRegisteredPlugins()
        {
            lock (Gate)
            {
                return Plugins
                    .OrderBy(
                        pair =>
                            pair.Key,
                        StringComparer.Ordinal)
                    .Select(
                        pair =>
                            pair.Value)
                    .ToArray();
            }
        }


        public static IDisposable Attach(
            RuntimeFacade runtime,
            AIRuntimeEditorRuntimePluginContext context)
        {
            if (runtime ==
                null)
            {
                throw new ArgumentNullException(
                    nameof(runtime));
            }

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

            IReadOnlyList<KeyValuePair<
                string,
                IAIRuntimeEditorRuntimePlugin>> plugins =
                GetPluginSnapshot();

            List<IDisposable> attachments =
                new List<IDisposable>();

            foreach (
                KeyValuePair<
                    string,
                    IAIRuntimeEditorRuntimePlugin> registration
                in plugins)
            {
                try
                {
                    IDisposable attachment =
                        registration.Value.Attach(
                            runtime,
                            context);

                    if (attachment !=
                        null)
                    {
                        attachments.Add(
                            attachment);
                    }
                }
                catch (Exception exception)
                {
                    AIRuntimeAgentActivityStore.Shared.Publish(
                        context,
                        AIRuntimeAgentActivityKind.Error,
                        $"Editor runtime plugin '{registration.Key}' could not attach.",
                        exception.Message);
                }
            }

            return new CompositeAttachment(
                attachments);
        }


        private static IReadOnlyList<KeyValuePair<
                string,
                IAIRuntimeEditorRuntimePlugin>>
            GetPluginSnapshot()
        {
            lock (Gate)
            {
                return Plugins
                    .OrderBy(
                        pair =>
                            pair.Key,
                        StringComparer.Ordinal)
                    .ToArray();
            }
        }


        private static string NormalizePluginId(
            string value)
        {
            if (string.IsNullOrWhiteSpace(
                    value))
            {
                throw new ArgumentException(
                    "An AIRuntime Editor runtime plugin ID is required.",
                    nameof(value));
            }

            string normalized =
                value.Trim();

            if (!string.Equals(
                    value,
                    normalized,
                    StringComparison.Ordinal))
            {
                throw new ArgumentException(
                    "AIRuntime Editor runtime plugin IDs cannot contain leading or trailing whitespace.",
                    nameof(value));
            }

            return normalized;
        }


        private static void Unregister(
            string pluginId,
            IAIRuntimeEditorRuntimePlugin plugin)
        {
            if (string.Equals(
                    pluginId,
                    AIRuntimeAgentActivityRuntimePlugin.PluginId,
                    StringComparison.Ordinal))
            {
                return;
            }

            lock (Gate)
            {
                if (Plugins.TryGetValue(
                        pluginId,
                        out IAIRuntimeEditorRuntimePlugin current) &&
                    ReferenceEquals(
                        current,
                        plugin))
                {
                    Plugins.Remove(
                        pluginId);

                    _revision++;
                }
            }
        }


        private sealed class PluginRegistration :
            IDisposable
        {
            private readonly string _pluginId;
            private IAIRuntimeEditorRuntimePlugin _plugin;


            public PluginRegistration(
                string pluginId,
                IAIRuntimeEditorRuntimePlugin plugin)
            {
                _pluginId = pluginId;
                _plugin = plugin;
            }


            public void Dispose()
            {
                IAIRuntimeEditorRuntimePlugin plugin =
                    Interlocked.Exchange(
                        ref _plugin,
                        null);

                if (plugin !=
                    null)
                {
                    Unregister(
                        _pluginId,
                        plugin);
                }
            }
        }


        private sealed class CompositeAttachment :
            IDisposable
        {
            private List<IDisposable> _attachments;


            public CompositeAttachment(
                List<IDisposable> attachments)
            {
                _attachments =
                    attachments ??
                    new List<IDisposable>();
            }


            public void Dispose()
            {
                List<IDisposable> attachments =
                    Interlocked.Exchange(
                        ref _attachments,
                        null);

                if (attachments ==
                    null)
                {
                    return;
                }

                for (
                    int index = attachments.Count - 1;
                    index >= 0;
                    index--)
                {
                    try
                    {
                        attachments[index]
                            ?.Dispose();
                    }
                    catch
                    {
                        // One plugin cannot prevent the rest from detaching.
                    }
                }
            }
        }
    }
}
