using System;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AIRuntime.Editor;
using NUnit.Framework;

namespace AIRuntime.Tests
{
    public sealed class KanbanComponentSourceScopeTests
    {
        private static Type Runner => typeof(AIRuntimeKanbanSession).Assembly.GetType("AIRuntime.Editor.AIRuntimeKanbanAgentRunner");

        [Test]
        public void InferredScaffoldingRequiresOwnedOutputsNotConsumedInputs()
        {
            Type type = Runner.GetNestedType("KanbanArtifactOwnershipMiddleware", BindingFlags.NonPublic);
            object middleware = Activator.CreateInstance(type, true);
            var activate = type.GetMethod("Activate");
            var canCreate = type.GetMethod("CanCreateDirectory");
            activate.Invoke(middleware, new object[] { Array.Empty<string>(), new[] { "Assets/Game/Scene.unity" }, Array.Empty<string>(), true });
            Assert.IsFalse((bool)canCreate.Invoke(middleware, new object[] { "Assets/Game" }));
            activate.Invoke(middleware, new object[] { new[] { "Assets/Game/Scene.unity" }, Array.Empty<string>(), Array.Empty<string>(), true });
            Assert.IsTrue((bool)canCreate.Invoke(middleware, new object[] { "Assets/Game" }));
            Assert.IsFalse((bool)canCreate.Invoke(middleware, new object[] { "Assets/GameOther" }));
        }

        [UnityEngine.TestTools.UnityTest]
        public System.Collections.IEnumerator ReviewEvidenceTimeoutCancelsReadAndReportsEvidenceGap()
        {
            var pending = new TaskCompletionSource<string>();
            CancellationToken observed = default;
            Func<CancellationToken, Task<string>> capture = token => { observed = token; return pending.Task; };
            var method = Runner.GetMethod("CaptureBoundedReviewEvidenceAsync", BindingFlags.Static | BindingFlags.NonPublic);
            var task = (Task<string>)method.Invoke(null, new object[] { capture, CancellationToken.None, TimeSpan.FromMilliseconds(20) });
            double limit = UnityEditor.EditorApplication.timeSinceStartup + 5;
            while (!task.IsCompleted && UnityEditor.EditorApplication.timeSinceStartup < limit) yield return null;
            Assert.IsTrue(task.IsCompleted, "Read deadline did not complete.");
            Assert.IsTrue(observed.IsCancellationRequested);
            Assert.IsTrue(task.IsFaulted);
            StringAssert.Contains("AIRUNTIME_REVIEW_EVIDENCE_INCOMPLETE", task.Exception.InnerException.Message);
            pending.SetResult("late result");
        }

        [Test]
        public void CompletedReviewEvidenceIsPreserved()
        {
            var method = Runner.GetMethod("CaptureBoundedReviewEvidenceAsync", BindingFlags.Static | BindingFlags.NonPublic);
            Func<CancellationToken, Task<string>> capture = token => Task.FromResult("authoritative evidence");
            var task = (Task<string>)method.Invoke(null, new object[] { capture, CancellationToken.None, TimeSpan.FromSeconds(1) });
            Assert.IsTrue(task.IsCompleted);
            Assert.AreEqual("authoritative evidence", task.Result);
        }

        [Test]
        public void CrossSceneReferencesAreRejectedBeforeAssignment()
        {
            var a = UnityEditor.SceneManagement.EditorSceneManager.NewPreviewScene();
            var b = UnityEditor.SceneManagement.EditorSceneManager.NewPreviewScene();
            try
            {
                var owner = new UnityEngine.GameObject("Owner");
                var local = new UnityEngine.GameObject("Local");
                var foreign = new UnityEngine.GameObject("Foreign");
                UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(owner, a);
                UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(local, a);
                UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(foreign, b);
                var utility = Runner.Assembly.GetType("AIRuntime.Editor.EditorCapabilityUtility");
                var validate = utility.GetMethod("ValidateSceneReference", BindingFlags.Static | BindingFlags.NonPublic);
                Assert.DoesNotThrow(() => validate.Invoke(null, new object[] { owner.transform, local.transform }));
                Assert.DoesNotThrow(() => validate.Invoke(null, new object[] { owner, null }));
                var error = Assert.Throws<TargetInvocationException>(() => validate.Invoke(null, new object[] { owner.transform, foreign.transform }));
                StringAssert.Contains("Cross-scene references cannot be saved", error.InnerException.Message);
            }
            finally
            {
                UnityEditor.SceneManagement.EditorSceneManager.ClosePreviewScene(a);
                UnityEditor.SceneManagement.EditorSceneManager.ClosePreviewScene(b);
            }
        }

        private static Func<string, bool> Scope(bool enforce)
        {
            Type type = Runner.GetNestedType("KanbanArtifactOwnershipMiddleware", BindingFlags.NonPublic);
            object middleware = Activator.CreateInstance(type, true);
            type.GetMethod("Activate").Invoke(middleware, new object[] {
                new[] { "Assets/Game/Owned.cs" }, new[] { "Assets/Game/Input.cs" }, Array.Empty<string>(), enforce });
            return path => (bool)type.GetMethod("CanReadPath").Invoke(middleware, new object[] { path });
        }

        [Test]
        public void CollectorPolicyIncludesOwnedAndDeclaredInputsButNotPackagesOrSiblings()
        {
            var readable = Scope(true);
            Assert.IsTrue(readable("Assets/Game/Owned.cs"));
            Assert.IsTrue(readable("Assets\\Game\\Input.cs"));
            Assert.IsFalse(readable("Assets/Game/UnfinishedSibling.cs"));
            Assert.IsFalse(readable("Packages/com.unity.ugui/Runtime/UI/Core/GraphicRaycaster.cs"));
            Assert.IsTrue(Scope(false)("Packages/com.unity.ugui/Runtime/UI/Core/GraphicRaycaster.cs"));
        }

        [Test]
        public void ExcludedComponentSourcesKeepInspectionEvidenceWithoutExecutingReads()
        {
            var evidence = new StringBuilder("Existing authoritative component inspection.");
            var append = Runner.GetMethod("AppendScriptEvidenceAsync", BindingFlags.Static | BindingFlags.NonPublic);
            var result = (Task)append.Invoke(null, new object[] { null, evidence,
                new[] { "Packages/com.unity.ugui/Runtime/UI/Core/GraphicRaycaster.cs", "Assets/Game/UnfinishedSibling.cs" },
                CancellationToken.None, Scope(true) });
            // All sources are excluded, so this path completes synchronously and
            // must not touch the null runtime or dispatch any read capability.
            Assert.IsTrue(result.IsCompleted);
            Assert.IsFalse(result.IsFaulted, result.Exception?.ToString());
            StringAssert.Contains("Existing authoritative component inspection.", evidence.ToString());
            StringAssert.Contains("GraphicRaycaster.cs", evidence.ToString());
            StringAssert.Contains("UnfinishedSibling.cs", evidence.ToString());
            StringAssert.Contains("no source-content verification is claimed", evidence.ToString());
        }
    }
}

