using System;
using System.Collections;
using System.Threading;
using System.Threading.Tasks;
using AIRuntime.Editor;
using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Permissions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;

namespace AIRuntime.Tests
{
    public sealed class GameplayReadinessTests
    {
        private string _folder, _path, _prefab;
        private Scene _scene;
        [SetUp] public void Setup()
        {
            _folder = "Assets/__Readiness_" + Guid.NewGuid().ToString("N");
            AssetDatabase.CreateFolder("Assets", _folder.Substring(7));
            _path = _folder + "/Fixture.unity";
            Assert.IsTrue(AssetDatabase.CopyAsset(AssetDatabase.GUIDToAssetPath("edb4629b297e4a26bb4d3a4275537131"), _path));
            _scene = EditorSceneManager.OpenScene(_path, OpenSceneMode.Additive);
            var go = new GameObject("ReferencedBody", typeof(Rigidbody), typeof(HingeJoint));
            SceneManager.MoveGameObjectToScene(go, _scene);
            go.GetComponent<Rigidbody>().mass = 3;
            _prefab = _folder + "/Body.prefab";
            var prefab = PrefabUtility.SaveAsPrefabAsset(go, _prefab);
            UnityEngine.Object.DestroyImmediate(go);
            PrefabUtility.InstantiatePrefab(prefab, _scene);
            EditorSceneManager.SaveScene(_scene);
        }
        [TearDown] public void TearDown()
        {
            if (_scene.IsValid() && _scene.isLoaded) EditorSceneManager.CloseScene(_scene, true);
            AssetDatabase.DeleteAsset(_folder);
        }
        private static IEnumerator Await(Task task)
        {
            double limit = EditorApplication.timeSinceStartup + 10;
            while (!task.IsCompleted && EditorApplication.timeSinceStartup < limit) yield return null;
            Assert.IsTrue(task.IsCompleted, "Inspection exceeded its test deadline.");
            if (task.IsFaulted) throw task.Exception.InnerException;
        }
        private Task<object> Inspect(ICapability capability, bool startup = false) => capability.ExecuteObjectAsync(
            new InspectGameplayReadinessArguments { ScenePath = _path, RunStartupCheck = startup },
            new CapabilityContext { CancellationToken = CancellationToken.None });

        [UnityTest] public IEnumerator ReportsScenePrefabReferencesAndReusesOnlyUnchangedEvidence()
        {
            var capability = new InspectGameplayReadinessCapability();
            var task = Inspect(capability); yield return Await(task);
            var report = (JObject)task.Result;
            Assert.AreEqual(CapabilityPermission.Write, capability.Permission);
            Assert.AreEqual("NotRequested", report["Startup"]["Status"].Value<string>());
            Assert.IsFalse(report["Coverage"]["GameplayBehaviorVerified"].Value<bool>());
            Assert.AreEqual(1, report["Coverage"]["ReferencedPrefabs"].Value<int>());
            Assert.IsFalse(report["Coverage"]["Truncated"].Value<bool>());
            StringAssert.DoesNotContain("m_FileID", report.ToString());
            StringAssert.Contains("UnassignedReference", report.ToString());
            StringAssert.Contains("Rigidbody", report.ToString());
            StringAssert.Contains(_prefab, report.ToString());
            Assert.IsFalse(_scene.isDirty);
            var second = Inspect(capability); yield return Await(second);
            Assert.IsTrue(((JObject)second.Result)["Unchanged"].Value<bool>());
            _scene.GetRootGameObjects()[0].GetComponent<Rigidbody>().mass = 9;
            var third = Inspect(capability); yield return Await(third);
            Assert.AreNotEqual(report["ReportId"].Value<string>(), ((JObject)third.Result)["ReportId"].Value<string>());
        }
        [UnityTest] public IEnumerator StartupPreservesOtherLoadedScenesAndDoesNotEnterPlayMode()
        {
            var task = Inspect(new InspectGameplayReadinessCapability(), true); yield return Await(task);
            Assert.AreEqual("NotRun", ((JObject)task.Result)["Startup"]["Status"].Value<string>());
            Assert.IsFalse(EditorApplication.isPlayingOrWillChangePlaymode);
            Assert.IsTrue(_scene.isLoaded);
        }
        [UnityTest] public IEnumerator BoundedReportDisclosesTruncation()
        {
            for (int i = 0; i < 210; i++) { var go = new GameObject("Extra" + i); SceneManager.MoveGameObjectToScene(go, _scene); }
            var task = Inspect(new InspectGameplayReadinessCapability()); yield return Await(task);
            var report = (JObject)task.Result;
            Assert.IsTrue(report["Coverage"]["Truncated"].Value<bool>());
            Assert.Less(report.ToString(Newtonsoft.Json.Formatting.None).Length, 152000);
        }
        private sealed class Scope : IKanbanTaskExecutionServices
        {
            public object Context;
            public object ReadTaskContext() => Context;
            public object CreateChildTask(CreateKanbanChildTaskArguments a) => throw new NotSupportedException();
            public object CommitPlan() => throw new NotSupportedException();
        }
        [UnityTest] public IEnumerator ScopedReportSkipsUndeclaredPrefabsAndRejectsUndeclaredScenes()
        {
            var scope = new Scope { Context = new { IsChildTask = true, ProducedArtifactPaths = new string[0], ConsumedArtifactPaths = new[] { _path } } };
            var capability = (ICapability)Activator.CreateInstance(typeof(InspectGameplayReadinessCapability),
                System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new object[] { scope, null }, null);
            var task = Inspect(capability); yield return Await(task);
            var report = (JObject)task.Result;
            Assert.AreEqual(0, report["Coverage"]["ReferencedPrefabs"].Value<int>());
            StringAssert.Contains("outside declared read scope", report["Coverage"].ToString());
            scope.Context = new { IsChildTask = true, ProducedArtifactPaths = new string[0], ConsumedArtifactPaths = new string[0] };
            var denied = Inspect(capability);
            while (!denied.IsCompleted) yield return null;
            Assert.IsTrue(denied.IsFaulted);
            StringAssert.Contains("ArtifactOwnership", denied.Exception.InnerException.Message);
        }
        [Test] public void DiscoveryUsesNoProgressGuardInEditMode()
        {
            Assert.AreEqual(CapabilityObservationBehavior.StableUntilMutation, new FindGameObjectsCapability().ObservationBehavior);
        }
    }
}
