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

namespace NcProgramManager.Tests.Unit
{
    [TestFixture]
    public class HeidenhainMachineTests
    {
        private HeidenhainConnectionConfig _config;
        private Mock<ILsv2Client> _clientMock;
        private Mock<INcProgramValidator> _validatorMock;
        private HeidenhainMachine _machine;

        [SetUp]
        public void SetUp()
        {
            _config = new HeidenhainConnectionConfig
            {
                Name = "TestHeidenhain",
                IpAddress = "192.168.1.4",
                Port = 19000
            };
            _clientMock = new Mock<ILsv2Client>(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());
            // Internal constructor sets state = Connected immediately
            _machine = new HeidenhainMachine(_config, _clientMock.Object, _validatorMock.Object);
        }

        [TearDown]
        public void TearDown()
        {
            _clientMock.Setup(c => c.Disconnect());
            _machine?.Dispose();
        }

        // State on construction

        [Test]
        public void Constructor_WithInjectedClient_StateIsConnected()
        {
            Assert.That(_machine.State, Is.EqualTo(ConnectionState.Connected));
        }

        // ReadProgramAsync

        [Test]
        public void ReadProgramAsync_ReceiveFileReturnsContent_ResultOk()
        {
            _clientMock.Setup(c => c.ReceiveFile("TNC:/nc_prog/O1234.H")).Returns("NC_CONTENT");

            var result = _machine.ReadProgramAsync("TNC:/nc_prog/O1234.H").GetAwaiter().GetResult();

            Assert.That(result.Success, Is.True);
            Assert.That(result.Value, Is.EqualTo("NC_CONTENT"));
            _clientMock.Verify(c => c.ReceiveFile("TNC:/nc_prog/O1234.H"), Times.Once);
        }

        [Test]
        public void ReadProgramAsync_ReceiveFileReturnsNull_ResultIsFail()
        {
            _clientMock.Setup(c => c.ReceiveFile(It.IsAny<string>())).Returns((string)null);

            var result = _machine.ReadProgramAsync("TNC:/nc_prog/MISSING.H").GetAwaiter().GetResult();

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

        // WriteProgramAsync

        [Test]
        public void WriteProgramAsync_WithName_AppendsDotHExtension()
        {
            _clientMock.Setup(c => c.SendFile("TNC:/nc_prog/O1234.H", "CONTENT")).Returns(true);

            var prog = new CncProgram { Name = "O1234", Content = "CONTENT" };
            var result = _machine.WriteProgramAsync("TNC:/nc_prog/", prog).GetAwaiter().GetResult();

            Assert.That(result.Success, Is.True);
            _clientMock.Verify(c => c.SendFile("TNC:/nc_prog/O1234.H", "CONTENT"), Times.Once);
        }

        [Test]
        public void WriteProgramAsync_NameAlreadyHasExtension_NoDoubleExtension()
        {
            _clientMock.Setup(c => c.SendFile("TNC:/nc_prog/O1234.H", "CONTENT")).Returns(true);

            var prog = new CncProgram { Name = "O1234.H", Content = "CONTENT" };
            _machine.WriteProgramAsync("TNC:/nc_prog/", prog).GetAwaiter().GetResult();

            _clientMock.Verify(c => c.SendFile("TNC:/nc_prog/O1234.H", "CONTENT"), Times.Once);
            _clientMock.Verify(c => c.SendFile(It.Is<string>(s => s.EndsWith(".H.H")), It.IsAny<string>()), Times.Never);
        }

        [Test]
        public void WriteProgramAsync_EmptyContent_FailsWithoutCallingSendFile()
        {
            var prog = new CncProgram { Name = "O1234", Content = "" };
            var result = _machine.WriteProgramAsync("TNC:/nc_prog/", prog).GetAwaiter().GetResult();

            Assert.That(result.Success, Is.False);
            _clientMock.Verify(c => c.SendFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
        }

        [Test]
        public void WriteProgramAsync_NullProgram_FailsWithoutCallingSendFile()
        {
            var result = _machine.WriteProgramAsync("TNC:/nc_prog/", null).GetAwaiter().GetResult();

            Assert.That(result.Success, Is.False);
            _clientMock.Verify(c => c.SendFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
        }

        [Test]
        public void WriteProgramAsync_SendFileFails_ResultIsFail()
        {
            _clientMock.Setup(c => c.SendFile(It.IsAny<string>(), It.IsAny<string>())).Returns(false);

            var prog = new CncProgram { Name = "O1234", Content = "DATA" };
            var result = _machine.WriteProgramAsync("TNC:/nc_prog/", prog).GetAwaiter().GetResult();

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

        // ReadActiveProgramAsync

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

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