using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Shouldly;
using Xunit;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;

namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
{
    public class LlmIntegrationCommandTests
    {
        [Theory]
        [InlineData("capabilities", false, "")]
        [InlineData("object snapshot", false, "")]
        [InlineData("object lineage", false, "")]
        [InlineData("object children", false, "")]
        [InlineData("object attribute value get", false, "")]
        [InlineData("object attribute value set", true, "name")]
        [InlineData("object scripts list", false, "")]
        [InlineData("object scripts set", true, "name")]
        [InlineData("object scripts create", true, "name")]
        [InlineData("object scripts settings set", true, "name")]
        [InlineData("area create", true, "template")]
        [InlineData("engine create", true, "template")]
        [InlineData("instance assign-area", true, "name")]
        [InlineData("io assign", true, "name")]
        [InlineData("batch", false, "")]
        [InlineData("validate", false, "")]
        public void CapabilityRegistry_IncludesLlmCommands(string commandName, bool mutates, string confirmTarget)
        {
            var command = CommandCapabilityRegistry.Find(commandName);

            command.ShouldNotBeNull();
            command.Mutates.ShouldBe(mutates);
            command.ConfirmTarget.ShouldBe(confirmTarget);
            command.SupportsLlmJson.ShouldBeTrue();
        }

        [Fact]
        public void CapabilityValidation_RejectsMissingMutationConfirmation()
        {
            var result = CommandCapabilityRegistry.Validate(
                "object attribute value set",
                new Dictionary<string, object>
                {
                    ["galaxy"] = "ZB",
                    ["name"] = "TestMachine",
                    ["attribute"] = "Description",
                    ["value"] = "Updated"
                },
                requireMutationConfirmation: true);

            result.Valid.ShouldBeFalse();
            result.Errors.ShouldContain("Missing required confirm=true.");
            result.Errors.ShouldContain("confirm-target must be 'TestMachine'.");
        }

        [Fact]
        public void CapabilityValidation_AcceptsConfirmedMutation()
        {
            var result = CommandCapabilityRegistry.Validate(
                "object attribute value set",
                new Dictionary<string, object>
                {
                    ["galaxy"] = "ZB",
                    ["name"] = "TestMachine",
                    ["attribute"] = "Description",
                    ["value"] = "Updated",
                    ["confirm"] = true,
                    ["confirm-target"] = "TestMachine"
                },
                requireMutationConfirmation: true);

            result.Valid.ShouldBeTrue();
        }

        [Fact]
        public void LlmResponse_EnvelopeSerializesSuccessAndUnavailable()
        {
            var json = LlmResponse.Ok(
                "object snapshot",
                "ZB",
                "TestMachine",
                new { name = "TestMachine" },
                unavailable: new[] { new LlmUnavailableField { Field = "scripts", Reason = "not exposed" } });

            var token = JObject.Parse(json);

            token["success"]?.Value<bool>().ShouldBeTrue();
            token["command"]?.Value<string>().ShouldBe("object snapshot");
            token["data"]?["name"]?.Value<string>().ShouldBe("TestMachine");
            token["unavailable"]?.Count().ShouldBe(1);
        }

        [Fact]
        public void SplitCommandName_UsesRegistrySubcommand()
        {
            var split = CommandCapabilityRegistry.SplitCommandName("object attribute value get");

            split.Command.ShouldBe("object");
            split.Subcommand.ShouldBe("attribute-value-get");
        }

        [Fact]
        public void PackageSnapshotParser_ExtractsLineageAttributesAndScripts()
        {
            var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".aaPKG");
            try
            {
                File.WriteAllText(path, @"
Lineage: $gMachine -> $TestMachine
AttributeValue Name=""Description"" DataType=""MxString"" Value=""Test machine template""
ScriptBody Name=""UpdateTestChangingInt.ExecuteText"">
me.TestChangingInt = me.TestChangingInt + 1;
</ScriptBody>
");

                var snapshot = PackageSnapshotParser.Parse(path);

                snapshot.PackageFallbackUsed.ShouldBeTrue();
                snapshot.Lineage.ShouldBe(new[] { "$gMachine", "$TestMachine" });
                snapshot.AttributeValues.Single().Name.ShouldBe("Description");
                snapshot.AttributeValues.Single().Value.ShouldBe("Test machine template");
                snapshot.ScriptBodies.Single().Name.ShouldBe("UpdateTestChangingInt.ExecuteText");
                snapshot.ScriptBodies.Single().Body.ShouldContain("TestChangingInt");
            }
            finally
            {
                if (File.Exists(path))
                    File.Delete(path);
            }
        }

        [Fact]
        public void PackageSnapshotParser_ExtractsNestedBinaryScriptExtensionBody()
        {
            var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".aaPKG");
            try
            {
                var innerBytes = Encoding.Unicode.GetBytes(string.Join("\n", new[]
                {
                    "UpdateTestChangingInt",
                    "UpdateTestChangingInt_ScriptExtension",
                    "Me.TestChangingInt = System.Random().Next(1,1000);",
                    "Periodic"
                }));

                using (var outer = ZipFile.Open(path, ZipArchiveMode.Create))
                {
                    var entry = outer.CreateEntry("File1.cab");
                    using (var entryStream = entry.Open())
                    using (var inner = new ZipArchive(entryStream, ZipArchiveMode.Create))
                    {
                        var txt = inner.CreateEntry("1055.txt");
                        using (var txtStream = txt.Open())
                            txtStream.Write(innerBytes, 0, innerBytes.Length);
                    }
                }

                var snapshot = PackageSnapshotParser.Parse(path);
                var script = snapshot.ScriptBodies.Single(s => s.Name == "UpdateTestChangingInt.ExecuteText");

                script.Body.ShouldBe("Me.TestChangingInt = System.Random().Next(1,1000);");
                script.Source.ShouldBe("export-package:binary");
                snapshot.AttributeValues.Single(a => a.Name == "UpdateTestChangingInt.ExecuteText").Value.ShouldBe(script.Body);
            }
            finally
            {
                if (File.Exists(path))
                    File.Delete(path);
            }
        }
    }
}
