using System;
using NUnit.Framework;
using NcProgramManager.Cnc.Models;
using NcProgramManager.Cnc.Mazak;
using NcProgramManager.Tests.Integration.Stubs;

namespace NcProgramManager.Tests.Integration
{
    [TestFixture]
    [Category("Integration")]
    public class MazakMachineIntegrationTests
    {
        private FtpServerStub _stub;
        private MazakMachine _machine;
        private const string ProgramDir = "/PRG/";

        [SetUp]
        public void SetUp()
        {
            _stub = new FtpServerStub();
            var config = new MazakConnectionConfig
            {
                Name = "MazakIntegration",
                IpAddress = "127.0.0.1",
                Port = _stub.Port,
                Username = "user",
                Password = "pass",
                ProgramDirectory = ProgramDir,
                RequestTimeout = TimeSpan.FromSeconds(5)
            };
            _machine = new MazakMachine(config);
        }

        [TearDown]
        public void TearDown()
        {
            _machine?.Dispose();
            _stub?.Dispose();
        }

        [Test]
        public void ConnectAsync_StubListens_ResultOk()
        {
            var result = _machine.ConnectAsync().GetAwaiter().GetResult();

            Assert.That(result.Success, Is.True);
            Assert.That(_machine.State, Is.EqualTo(ConnectionState.Connected));
        }

        [Test]
        public void WriteThenRead_ProgramRoundTripsSamePathNoExtension()
        {
            _machine.ConnectAsync().GetAwaiter().GetResult();
            // Bare identifier "1234"; Mazak validator is passthrough but use clean EIA text.
            const string identifier = "1234";
            string content = "O1234\nG00 X10\nM30";
            var prog = new CncProgram { Name = identifier, Content = content };

            // Write target == ResolvePath(identifier) == ProgramDir + identifier (verbatim, no extension)
            var writeResult = _machine.WriteProgramAsync(identifier, prog).GetAwaiter().GetResult();
            Assert.That(writeResult.Success, Is.True);

            string storedPath = ProgramDir + identifier;
            Assert.That(_stub.Files.ContainsKey(storedPath), Is.True,
                "File should be stored verbatim at " + storedPath);
            Assert.That(_stub.Files.ContainsKey(storedPath + ".EIA"), Is.False,
                "File must NOT be stored with an appended extension");

            // Read SAME bare identifier -> ResolvePath resolves to the same target (ISS-020 symmetry)
            var readResult = _machine.ReadProgramAsync(identifier).GetAwaiter().GetResult();
            Assert.That(readResult.Success, Is.True);
            Assert.That(readResult.Value, Is.EqualTo(content),
                "Read content must match written content for the same identifier (ResolvePath symmetry)");
        }

        [Test]
        public void WriteThenDelete_ProgramRemoved_SubsequentReadFails()
        {
            _machine.ConnectAsync().GetAwaiter().GetResult();
            const string identifier = "1234";
            string content = "O1234\nG00 X10\nM30";
            var prog = new CncProgram { Name = identifier, Content = content };

            var writeResult = _machine.WriteProgramAsync(identifier, prog).GetAwaiter().GetResult();
            Assert.That(writeResult.Success, Is.True);

            string storedPath = ProgramDir + identifier;
            Assert.That(_stub.Files.ContainsKey(storedPath), Is.True);

            // ISS-020 symmetry: delete the SAME bare identifier used for write.
            // ResolvePath(identifier) -> storedPath, so write/delete/read all align.
            var deleteResult = _machine.DeleteProgramAsync(identifier).GetAwaiter().GetResult();
            Assert.That(deleteResult.Success, Is.True);
            Assert.That(_stub.Files.ContainsKey(storedPath), Is.False);

            var readResult = _machine.ReadProgramAsync(identifier).GetAwaiter().GetResult();
            Assert.That(readResult.Success, Is.False, "Read after delete must fail / not found");
        }

        [Test]
        public void ReadProgramAsync_FileNotInStub_ResultFail()
        {
            _machine.ConnectAsync().GetAwaiter().GetResult();

            var result = _machine.ReadProgramAsync("9999").GetAwaiter().GetResult();

            Assert.That(result.Success, Is.False);
        }

        [Test]
        [Category("Hardware")]
        public void Hardware_ConnectAsync_RealMachine()
        {
            string ip = HardwareTestHelper.GetIpOrSkip("MAZAK_TEST_IP");
            var config = new MazakConnectionConfig
            {
                Name             = "MazakHardware",
                IpAddress        = ip,
                Port             = 21,
                Username         = Environment.GetEnvironmentVariable("MAZAK_USER") ?? "user",
                Password         = Environment.GetEnvironmentVariable("MAZAK_PASS") ?? "pass",
                ProgramDirectory = "/PRG/",
                RequestTimeout   = TimeSpan.FromSeconds(10)
            };
            using (var machine = new MazakMachine(config))
            {
                var result = machine.ConnectAsync().GetAwaiter().GetResult();
                Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors));
            }
        }
    }
}
