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

using AIRuntime.Editor;

using NUnit.Framework;


namespace AIRuntime.Tests.EditMode
{
    public sealed class KanbanAssociationReconcilerTests
    {
        [Test]
        public void LateProducer_BackfillsExistingCrossBranchConsumer()
        {
            var consumer = Task(
                "consumer",
                parent: "scene-parent",
                root: "root",
                consumes: new[] { "Assets/Game/Scripts/Goal.cs" });

            var producer = Task(
                "producer",
                parent: "script-parent",
                root: "root",
                createdAt: "2026-08-22T00:01:00Z",
                produces: new[] { "assets\\game\\scripts\\Goal.cs" });

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(consumer, producer);

            CollectionAssert.Contains(
                results["consumer"].DependencyTaskIds,
                "producer");
            CollectionAssert.Contains(
                results["consumer"].AssociatedTaskIds,
                "producer");
            CollectionAssert.Contains(
                results["producer"].AssociatedTaskIds,
                "consumer");
        }


        [Test]
        public void DuplicateOwnership_AssociatesSymmetricallyWithoutAncestorDeadlock()
        {
            var parent = Task(
                "parent",
                root: "root",
                produces: new[] { "Assets/Game/Scripts/GameManager.cs" });

            var recursiveChild = Task(
                "recursive-child",
                parent: "parent",
                root: "root",
                createdAt: "2026-08-22T00:01:00Z",
                produces: new[] { "Assets/Game/Scripts/GameManager.cs" },
                consumes: new[] { "Assets/Game/Scripts/GameManager.cs" });

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(parent, recursiveChild);

            CollectionAssert.Contains(
                results["parent"].AssociatedTaskIds,
                "recursive-child");
            CollectionAssert.Contains(
                results["recursive-child"].AssociatedTaskIds,
                "parent");
            CollectionAssert.DoesNotContain(
                results["recursive-child"].DependencyTaskIds,
                "parent",
                "Containment already blocks the parent; the reverse edge would deadlock both cards.");
        }


        [Test]
        public void MutableResourceWriters_UseStableCreatedOrderAcrossBranches()
        {
            var last = Task(
                "last",
                parent: "branch-b",
                root: "root",
                createdAt: "2026-08-22T00:03:00Z",
                mutates: new[] { "scene:Assets/Game/Scenes/Main.unity" });

            var first = Task(
                "first",
                parent: "branch-a",
                root: "root",
                createdAt: "2026-08-22T00:01:00Z",
                mutates: new[] { "SCENE:assets\\game\\scenes\\main.unity" });

            var middle = Task(
                "middle",
                parent: "branch-c",
                root: "root",
                createdAt: "2026-08-22T00:02:00Z",
                mutates: new[] { "scene:Assets/Game/Scenes/Main.unity" });

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(last, first, middle);

            CollectionAssert.Contains(results["middle"].DependencyTaskIds, "first");
            CollectionAssert.Contains(results["last"].DependencyTaskIds, "middle");
            CollectionAssert.AreEquivalent(
                new[] { "middle", "last" },
                results["first"].AssociatedTaskIds);
        }


        [Test]
        public void ExplicitDependencies_ArePreservedAndAssociated()
        {
            var prerequisite = Task("prerequisite", root: "root");
            var dependent = Task(
                "dependent",
                root: "root",
                dependencies: new[] { "prerequisite", "external-task" });

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(prerequisite, dependent);

            CollectionAssert.AreEquivalent(
                new[] { "prerequisite", "external-task" },
                results["dependent"].DependencyTaskIds);
            CollectionAssert.Contains(
                results["prerequisite"].AssociatedTaskIds,
                "dependent");
        }


        [Test]
        public void FinalVerifier_RemainsSiblingGraphSinkWhenLateCardAppears()
        {
            var implementation = Task("implementation", parent: "parent", root: "root");
            var verifier = Task(
                "verifier",
                parent: "parent",
                root: "root",
                finalVerifier: true);
            var lateOutcome = Task(
                "late-outcome",
                parent: "parent",
                root: "root",
                createdAt: "2026-08-22T00:02:00Z");

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(implementation, verifier, lateOutcome);

            CollectionAssert.AreEquivalent(
                new[] { "implementation", "late-outcome" },
                results["verifier"].DependencyTaskIds);
            CollectionAssert.Contains(
                results["late-outcome"].AssociatedTaskIds,
                "verifier");
        }


        [Test]
        public void ReviewConsumersOfSameArtifact_AreAssociatedWithoutSerializing()
        {
            var firstReview = Task(
                "first-review",
                parent: "branch-a",
                root: "root",
                consumes: new[] { "Assets/Game/Scripts/GameManager.cs" });
            var secondReview = Task(
                "second-review",
                parent: "branch-b",
                root: "root",
                consumes: new[] { "Assets/Game/Scripts/GameManager.cs" });

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(firstReview, secondReview);

            CollectionAssert.Contains(
                results["first-review"].AssociatedTaskIds,
                "second-review");
            Assert.IsEmpty(results["first-review"].DependencyTaskIds);
            Assert.IsEmpty(results["second-review"].DependencyTaskIds);
        }


        [Test]
        public void SameTitleInDifferentRoots_DoesNotCreateAssociation()
        {
            var first = Task("first", root: "root-a", title: "Verify integration");
            var second = Task("second", root: "root-b", title: "Verify integration");

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(first, second);

            Assert.IsEmpty(results["first"].AssociatedTaskIds);
            Assert.IsEmpty(results["second"].AssociatedTaskIds);
        }


        [Test]
        public void Reconciliation_IsIdempotentAndInputOrderIndependent()
        {
            var producer = Task(
                "producer",
                root: "root",
                produces: new[] { "Assets/Game/Data.json" });
            var consumer = Task(
                "consumer",
                root: "root",
                createdAt: "2026-08-22T00:02:00Z",
                consumes: new[] { "Assets/Game/Data.json" });

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> forward =
                Reconcile(producer, consumer);
            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> reverse =
                Reconcile(consumer, producer);

            CollectionAssert.AreEqual(
                forward["consumer"].DependencyTaskIds,
                reverse["consumer"].DependencyTaskIds);
            CollectionAssert.AreEqual(
                forward["producer"].AssociatedTaskIds,
                reverse["producer"].AssociatedTaskIds);

            consumer.DependencyTaskIds = forward["consumer"].DependencyTaskIds;
            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> repeated =
                Reconcile(producer, consumer);

            CollectionAssert.AreEqual(
                forward["consumer"].DependencyTaskIds,
                repeated["consumer"].DependencyTaskIds);
        }


        [Test]
        public void DerivedDependency_DoesNotCreateCycleAgainstExplicitEdge()
        {
            var producer = Task(
                "producer",
                root: "root",
                produces: new[] { "Assets/Game/Data.json" },
                dependencies: new[] { "consumer" });
            var consumer = Task(
                "consumer",
                root: "root",
                consumes: new[] { "Assets/Game/Data.json" });

            IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult> results =
                Reconcile(producer, consumer);

            CollectionAssert.Contains(results["producer"].DependencyTaskIds, "consumer");
            CollectionAssert.DoesNotContain(results["consumer"].DependencyTaskIds, "producer");
            CollectionAssert.Contains(results["consumer"].AssociatedTaskIds, "producer");
        }


        private static IReadOnlyDictionary<string, AIRuntimeKanbanAssociationTaskResult>
            Reconcile(params AIRuntimeKanbanAssociationTaskSnapshot[] tasks)
        {
            return new AIRuntimeKanbanAssociationReconciler()
                .Reconcile(tasks)
                .ToDictionary(result => result.Id, StringComparer.Ordinal);
        }


        private static AIRuntimeKanbanAssociationTaskSnapshot Task(
            string id,
            string parent = "",
            string root = "",
            string title = "Task",
            string createdAt = "2026-08-22T00:00:00Z",
            IReadOnlyList<string> produces = null,
            IReadOnlyList<string> consumes = null,
            IReadOnlyList<string> mutates = null,
            IReadOnlyList<string> dependencies = null,
            bool finalVerifier = false)
        {
            return new AIRuntimeKanbanAssociationTaskSnapshot
            {
                Id = id,
                ParentTaskId = parent,
                RootTaskId = root,
                Title = title,
                Description = string.Empty,
                CreatedAt = createdAt,
                ProducedArtifactPaths = produces ?? Array.Empty<string>(),
                ConsumedArtifactPaths = consumes ?? Array.Empty<string>(),
                MutatedResourceKeys = mutates ?? Array.Empty<string>(),
                DependencyTaskIds = dependencies ?? Array.Empty<string>(),
                IsFinalVerifier = finalVerifier
            };
        }
    }
}
