using System;
using System.Collections.Generic;
using System.Reflection;

using AIRuntime.Runtime.Services.Agent;

using NUnit.Framework;


namespace AIRuntime.Tests.EditMode
{
    public sealed class AgentObserverCollectionTests
    {
        private sealed class RecordingObserver :
            IAgentObserver
        {
            public List<AgentEvent> Events { get; } =
                new List<AgentEvent>();


            public void OnAgentEvent(
                AgentEvent agentEvent)
            {
                Events.Add(
                    agentEvent);
            }
        }


        private sealed class ThrowingObserver :
            IAgentObserver
        {
            public void OnAgentEvent(
                AgentEvent agentEvent)
            {
                throw new InvalidOperationException(
                    "Observer failure");
            }
        }


        private sealed class RemovingObserver :
            IAgentObserver
        {
            private readonly AgentObserverCollection _collection;
            private readonly IAgentObserver _observerToRemove;


            public RemovingObserver(
                AgentObserverCollection collection,
                IAgentObserver observerToRemove)
            {
                _collection = collection;
                _observerToRemove = observerToRemove;
            }


            public void OnAgentEvent(
                AgentEvent agentEvent)
            {
                _collection.Remove(
                    _observerToRemove);
            }
        }


        [Test]
        public void ConsoleObserver_SkipsRoutineToolsButKeepsFailures()
        {
            var messages = new List<string>();
            UnityEngine.Application.LogCallback callback = (message, stack, type) => messages.Add(message);
            UnityEngine.Application.logMessageReceived += callback;
            try
            {
                var observer = new ConsoleAgentObserver();
                observer.OnAgentEvent(new AgentEvent(AgentEventType.ToolStarted, "test", "read_file", "large payload"));
                observer.OnAgentEvent(new AgentEvent(AgentEventType.ToolCompleted, "test", "read_file", "large result"));
                observer.OnAgentEvent(new AgentEvent(AgentEventType.ModelResponseNormalized, "test"));
                Assert.AreEqual(0, messages.Count);
                observer.OnAgentEvent(new AgentEvent(AgentEventType.ToolFailed, "test", "read_file", "access denied"));
                Assert.AreEqual(1, messages.Count);
                StringAssert.Contains("access denied", messages[0]);
            }
            finally { UnityEngine.Application.logMessageReceived -= callback; }
        }

        [Test]
        public void ConsoleObserver_VerboseRestoresRoutineOutput()
        {
            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Log, "[Agent] ToolStarted Tool=read_file");
            new ConsoleAgentObserver { Verbose = true }.OnAgentEvent(
                new AgentEvent(AgentEventType.ToolStarted, "test", "read_file"));
        }

        [Test]
        public void QuietLoggingMiddlewareStillExecutesAndPropagatesFailure()
        {
            var middleware = new AIRuntime.Runtime.Capabilities.LoggingMiddleware();
            bool called = false;
            middleware.InvokeAsync(null, context => { called = true; return System.Threading.Tasks.Task.CompletedTask; })
                .GetAwaiter().GetResult();
            Assert.IsTrue(called);
            Assert.Throws<InvalidOperationException>(() => middleware.InvokeAsync(null,
                context => throw new InvalidOperationException("test failure")).GetAwaiter().GetResult());
        }
        [Test]
        public void Publish_IsolatesObserverFailure()
        {
            AgentObserverCollection collection =
                new AgentObserverCollection();

            RecordingObserver recorder =
                new RecordingObserver();

            collection.Add(
                new ThrowingObserver());

            collection.Add(
                recorder);

            AgentEvent agentEvent =
                new AgentEvent(
                    AgentEventType.RunStarted,
                    "session");

            Assert.DoesNotThrow(
                () => collection.Publish(
                    agentEvent));

            CollectionAssert.AreEqual(
                new[]
                {
                    agentEvent
                },
                recorder.Events);
        }


        [Test]
        public void Publish_UsesSnapshotWhenObserverRemovesAnotherObserver()
        {
            AgentObserverCollection collection =
                new AgentObserverCollection();

            RecordingObserver recorder =
                new RecordingObserver();

            collection.Add(
                new RemovingObserver(
                    collection,
                    recorder));

            collection.Add(
                recorder);

            collection.Publish(
                new AgentEvent(
                    AgentEventType.RunStarted,
                    "first"));

            collection.Publish(
                new AgentEvent(
                    AgentEventType.RunStarted,
                    "second"));

            Assert.AreEqual(
                1,
                recorder.Events.Count);
        }


        [Test]
        public void Publish_RejectsNullEvent()
        {
            Assert.Throws<ArgumentNullException>(
                () => new AgentObserverCollection()
                    .Publish(
                        null));
        }


        [Test]
        public void ConsoleObserver_BoundsLargeCapabilityResults()
        {
            MethodInfo bound =
                typeof(ConsoleAgentObserver).GetMethod(
                    "BoundMessageForConsole",
                    BindingFlags.NonPublic |
                    BindingFlags.Static);

            Assert.NotNull(bound);

            string result =
                (string)bound.Invoke(
                    null,
                    new object[]
                    {
                        new string('x', 160000)
                    });

            Assert.Less(result.Length, 5000);
            StringAssert.Contains(
                "AIRuntime console result bounded; original characters=160000",
                result);
        }
    }
}
