using System;
using NUnit.Framework;
using Moq;
using NcProgramManager.Cnc;
using NcProgramManager.Cnc.Models;
using NcProgramManager.Cnc.Fagor;

namespace NcProgramManager.Tests.Unit
{
    [TestFixture]
    public class FagorMachineTests
    {
        private FagorConnectionConfig _config;
        private Mock<IFagorFtpClient> _ftpMock;
        private Mock<INcProgramValidator> _validatorMock;
        private FagorMachine _machine;

        [SetUp]
        public void SetUp()
        {
            _config = new FagorConnectionConfig
            {
                Name = "TestFagor",
                IpAddress = "192.168.1.2",
                Port = 21,
                ProgramDirectory = "/Users/Prg/",
                ProgramExtension = ".nc"
            };
            _ftpMock = new Mock<IFagorFtpClient>(MockBehavior.Strict);
            // Always-ok validator so existing write tests are not affected by validation rules
            _validatorMock = new Mock<INcProgramValidator>();
            _validatorMock.Setup(v => v.Validate(It.IsAny<string>(), It.IsAny<string>()))
                          .Returns(ValidationResult.Ok());
            _machine = new FagorMachine(_config, _ftpMock.Object, _validatorMock.Object);
        }

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

        // ConnectAsync

        [Test]
        public void ConnectAsync_PingTrue_StateConnectedAndResultOk()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(true);

            var result = _machine.ConnectAsync().GetAwaiter().GetResult();

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

        [Test]
        public void ConnectAsync_PingFalse_StateFaultedAndResultFail()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(false);

            var result = _machine.ConnectAsync().GetAwaiter().GetResult();

            Assert.That(result.Success, Is.False);
            Assert.That(_machine.State, Is.EqualTo(ConnectionState.Faulted));
        }

        // ReadProgramAsync

        [Test]
        public void ReadProgramAsync_FullPath_CallsDownloadFileWithPath()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(true);
            _ftpMock.Setup(f => f.DownloadFile("/Users/Prg/PROG1.nc")).Returns("CONTENT");
            _machine.ConnectAsync().GetAwaiter().GetResult();

            var result = _machine.ReadProgramAsync("/Users/Prg/PROG1.nc").GetAwaiter().GetResult();

            Assert.That(result.Success, Is.True);
            Assert.That(result.Value, Is.EqualTo("CONTENT"));
            _ftpMock.Verify(f => f.DownloadFile("/Users/Prg/PROG1.nc"), Times.Once);
        }

        [Test]
        public void ReadProgramAsync_ShortName_ResolvesWithDirectoryAndExtension()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(true);
            _ftpMock.Setup(f => f.DownloadFile("/Users/Prg/PROG1.nc")).Returns("DATA");
            _machine.ConnectAsync().GetAwaiter().GetResult();

            _machine.ReadProgramAsync("PROG1").GetAwaiter().GetResult();

            _ftpMock.Verify(f => f.DownloadFile("/Users/Prg/PROG1.nc"), Times.Once);
        }

        // WriteProgramAsync

        [Test]
        public void WriteProgramAsync_ValidContent_CallsUploadFile()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(true);
            _ftpMock.Setup(f => f.UploadFile(It.IsAny<string>(), "CONTENT"));
            _machine.ConnectAsync().GetAwaiter().GetResult();

            var prog = new CncProgram { Name = "PROG1", Content = "CONTENT" };
            var result = _machine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();

            Assert.That(result.Success, Is.True);
            _ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), "CONTENT"), Times.Once);
        }

        [Test]
        public void WriteProgramAsync_EmptyContent_FailsWithoutCallingUploadFile()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(true);
            _machine.ConnectAsync().GetAwaiter().GetResult();

            var prog = new CncProgram { Name = "PROG1", Content = "" };
            var result = _machine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();

            Assert.That(result.Success, Is.False);
            _ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
        }

        [Test]
        public void WriteProgramAsync_NullProgram_FailsWithoutCallingUploadFile()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(true);
            _machine.ConnectAsync().GetAwaiter().GetResult();

            var result = _machine.WriteProgramAsync("/Users/Prg/", null).GetAwaiter().GetResult();

            Assert.That(result.Success, Is.False);
            _ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
        }

        // DeleteProgramAsync

        [Test]
        public void DeleteProgramAsync_ValidPath_CallsDeleteFile()
        {
            _ftpMock.Setup(f => f.Ping()).Returns(true);
            _ftpMock.Setup(f => f.DeleteFile("/Users/Prg/PROG1.nc"));
            _machine.ConnectAsync().GetAwaiter().GetResult();

            var result = _machine.DeleteProgramAsync("/Users/Prg/PROG1.nc").GetAwaiter().GetResult();

            Assert.That(result.Success, Is.True);
            _ftpMock.Verify(f => f.DeleteFile("/Users/Prg/PROG1.nc"), Times.Once);
        }

        // ReadActiveProgramAsync

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

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

        // SelectMainProgramAsync

        [Test]
        public void SelectMainProgramAsync_ReturnsFailNotSupported()
        {
            var result = _machine.SelectMainProgramAsync("/Users/Prg/PROG1.nc").GetAwaiter().GetResult();

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

        // Validator-real tests (NcProgramValidator with CncManufacturer.Fagor)

        [Test]
        public void WriteProgramAsync_NameTooLong_ValidationFails()
        {
            var realMachine = new FagorMachine(_config, _ftpMock.Object,
                new NcProgramValidator(CncManufacturer.Fagor));
            try
            {
                _ftpMock.Setup(f => f.Ping()).Returns(true);
                realMachine.ConnectAsync().GetAwaiter().GetResult();

                var prog = new CncProgram { Name = new string('A', 25), Content = "G00 X10" };
                var result = realMachine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();

                Assert.That(result.Success, Is.False);
                _ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
            }
            finally { realMachine.Dispose(); }
        }

        [Test]
        public void WriteProgramAsync_NameContainsTilde_ValidationFails()
        {
            var realMachine = new FagorMachine(_config, _ftpMock.Object,
                new NcProgramValidator(CncManufacturer.Fagor));
            try
            {
                _ftpMock.Setup(f => f.Ping()).Returns(true);
                realMachine.ConnectAsync().GetAwaiter().GetResult();

                var prog = new CncProgram { Name = "MAñANA", Content = "G00 X10" };
                var result = realMachine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();

                Assert.That(result.Success, Is.False);
                _ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
            }
            finally { realMachine.Dispose(); }
        }
    }
}
