#nullable enable

using Cysharp.Threading.Tasks;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using UnityEngine;
using UVC.Data;
using UVC.Log;

namespace UVC.Tests.Data
{
    [TestFixture]
    public class MQTTPipeLineTests
    {
        private MQTTPipeLine pipeLine;
        private Dictionary<string, TestDataHandler> handlers;
        private Dictionary<string, DataMask> dataMasks;
        private readonly string[] topicNames = { "AGV", "CARRIER", "STOCKER_STACK", "ALL" };


        [SetUp]
        public void Setup()
        {
            // 기본 테스트 환경 설정
            pipeLine = new MQTTPipeLine("localhost", 1883);
            pipeLine.UseMockup = true; // 테스트에서는 항상 MockMQTTService 사용

            // 핸들러와 데이터 마스크 초기화
            handlers = new Dictionary<string, TestDataHandler>();
            dataMasks = new Dictionary<string, DataMask>();

            // 각 토픽별 핸들러와 데이터 마스크 설정
            foreach (var topic in topicNames)
            {
                handlers[topic] = new TestDataHandler();
                dataMasks[topic] = CreateDataMaskForTopic(topic);
            }
        }

        public async UniTask TestAll()
        {
            Setup();
            Debug.Log("===== MQTTPipeLine 테스트 시작 =====");
            // 하나씩 테스트 해야 함
            //await RunTestAsync(nameof(ExecutePipeLine_AllTopics_RegistersAndHandlesMessages), ExecutePipeLine_AllTopics_RegistersAndHandlesMessages);
            //await RunTestAsync(nameof(RemoveTopic_ShouldStopReceivingMessages), RemoveTopic_ShouldStopReceivingMessages);
            //await RunTestAsync(nameof(UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData), UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData);
            //await RunTestAsync(nameof(UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData), UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData);
            //RunTest(nameof(OnTopicMessage_ValidJsonObject_CallsHandler), OnTopicMessage_ValidJsonObject_CallsHandler);
            //RunTest(nameof(OnTopicMessage_JsonArray_CallsHandler), OnTopicMessage_JsonArray_CallsHandler);
            //RunTest(nameof(OnTopicMessage_EmptyMessage_DoesNotCallHandler), OnTopicMessage_EmptyMessage_DoesNotCallHandler);
            RunTest(nameof(OnTopicMessage_InvalidJson_DoesNotCallHandler), OnTopicMessage_InvalidJson_DoesNotCallHandler);
            Debug.Log("===== MQTTPipeLine 테스트 완료 =====");
        }

        private void RunTest(string testName, Action testAction)
        {
            try
            {
                Debug.Log($"테스트 시작: {testName}");
                testAction();
                Debug.Log($"테스트 성공: {testName}");
            }
            catch (Exception ex)
            {
                Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}");
            }
        }

        private async UniTask RunTestAsync(string testName, Func<UniTask> testAction)
        {
            try
            {
                Debug.Log($"테스트 시작: {testName}");
                await testAction();
                Debug.Log($"테스트 성공: {testName}");
            }
            catch (Exception ex)
            {
                Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}");
            }
        }

        [TearDown]
        public void TearDown()
        {
            // 테스트 완료 후 리소스 정리
            pipeLine.Stop();
            pipeLine.Dispose();
        }

        private DataMask CreateDataMaskForTopic(string topic)
        {
            var dataMask = new DataMask();
            dataMask.ObjectName = topic;

            // 각 토픽별로 ObjectIdKey 설정
            switch (topic)
            {
                case "AGV":
                    dataMask.ObjectIdKey = "VHL_NAME";
                    dataMask.ObjectName = "AGV";
                    dataMask["VHL_NAME"] = "HFF09CNA8013";
                    dataMask["AGV_IDX"] = 12;
                    dataMask["B_INSTALL"] = "Y";
                    dataMask["NODE_ID"] = 235;
                    dataMask["REAL_ID"] = 235;
                    dataMask["VHL_STATE"] = 11;
                    dataMask["BAY_LIST"] = "21;";
                    dataMask["X"] = 118301;
                    dataMask["Y"] = 20177;
                    dataMask["MODE"] = 1;
                    dataMask["BATT"] = 75;
                    dataMask["SUB_GOAL"] = 211;
                    dataMask["FINAL_GOAL"] = 1006;
                    dataMask["TIMESTAMP"] = "2025-03-25T12:00:00.980Z";
                    dataMask["DEGREE"] = 181.2;
                    dataMask["STOP_STATE"] = 0;
                    dataMask["JOB_ID"] = "2F24217_289_7038296224059039";
                    dataMask["DESTINATION_PORT"] = "HFF09MPI0200_LIP01";
                    dataMask["SOURCE_PORT"] = "HFF09AGM0100_UOP01";
                    dataMask["FROM"] = "HFF09AGM0100,NULL,0201012";
                    dataMask["TO"] = "HFF09MPI0200,HFF09MPI0200_LIP01,NULL";
                    dataMask["TRANSPORT_JOB_TIMESTAMP"] = "2025-03-25T05:40:19.000Z";
                    dataMask["FACTOR"] = 69.3;
                    dataMask["AGV_FACTOR_TIMESTAMP"] = DateTime.Now;
                    break;
                case "CARRIER":
                    dataMask.ObjectIdKey = "MAIN_CARR_ID";
                    dataMask.ObjectName = "Carrier";
                    dataMask["MAIN_CARR_ID"] = "2F02365";
                    dataMask["SUB_CARR_ID"] = "2F02365,2F70671,2F28723";
                    dataMask["CARR_SEQ"] = "3";
                    dataMask["CARR_USE"] = "EMPTY";
                    dataMask["CURRENTPORT"] = "HFF09CNV0300_ABP3003";
                    dataMask["MOVESTATUS"] = "ARRIVED";
                    dataMask["MOVEFLAG"] = "0";
                    dataMask["PROD_DETAIL_CODE"] = "E3A";
                    dataMask["ASSIGN_LOT_QTY"] = "0";
                    dataMask["CARR_SIZE_TYPE"] = "STACK3";
                    dataMask["ABNM_VALUE"] = "0";
                    dataMask["LINE_ID"] = "FM0I";
                    dataMask["TIMESTAMP"] = DateTime.Now;
                    dataMask["GOOD_QTY"] = "0";
                    dataMask["CURRENTLOCATION"] = "HFF09CNV0300";
                    break;
                case "STOCKER_STACK":
                    dataMask.ObjectIdKey = "STOCKER_NAME";
                    dataMask.ObjectName = "StokerStack";
                    dataMask["STOCKER_NAME"] = "HFF09AGN0300";
                    dataMask["CAPACITY"] = "89.57";
                    dataMask["MAXIMUM_CAPACITY"] = "834";
                    dataMask["TRAY_CAPACITY"] = "83.25";
                    dataMask["MAXIMUM_TRAY_CAPACITY"] = "2502";
                    dataMask["RACK_LOAD_COUNT"] = "747";
                    dataMask["RACK_EMPTY_COUNT"] = "87";
                    dataMask["RESERVATED_RETURN_COUNT"] = "5";
                    dataMask["TRAY_COUNT"] = "2083";
                    dataMask["TRAY_REWORK_COUNT_AVG"] = "3";
                    dataMask["TRAY_REWORK_COUNT_MAX"] = "153";
                    dataMask["TRAY_REWORK_COUNT_MIN"] = "0";
                    dataMask["RACK_DISABLE_COUNT"] = "4";
                    dataMask["KOR_EQP_NAME"] = "상온Aging #03";
                    dataMask["ENG_EQP_NAME"] = "상온Aging #03";
                    dataMask["TIMESTAMP"] = DateTime.Now;
                    dataMask["STEP"] = new List<DataMask>()
                    {
                        new DataMask {
                            ObjectIdKey = "STOCKER_NAME",
                            ObjectName = "StokerStep",
                            ["STOCKER_NAME"] = "HFF09AGN0300",
                            ["STEP_ID"] = "8106",
                            ["RACK_STEP_COUNT"] = "88",
                            ["TOTAL"] = "834",
                            ["STEP_CAPACITY"] = "10.55",
                        },
                    };
                    break;
                case "ALL":
                    // ALL 토픽은 ObjectIdKey 없음
                    dataMask["AGV"] = new List<DataMask>()
                    {
                        new DataMask
                        {
                            ["VHL_NAME"] = "HFF09CNA8053",
                            ["AGV_IDX"] = 52,
                            ["X"] = 223316,
                            ["Y"] = 218171,
                            ["B_INSTALL"] = "Y",
                            ["NODE_ID"] = 235,
                            ["REAL_ID"] = 235,
                            ["VHL_STATE"] = 11,
                            ["BAY_LIST"] = "21;",
                            ["MODE"] = 1,
                            ["BATT"] = 75,
                            ["SUB_GOAL"] = 211,
                            ["FINAL_GOAL"] = 1006,
                            ["TIMESTAMP"] = "2025-03-25T12:00:00.980Z",
                            ["DEGREE"] = 181.2,
                            ["STOP_STATE"] = 0,
                            ["JOB_ID"] = "2F24217_289_7038296224059039",
                            ["DESTINATION_PORT"] = "HFF09MPI0200_LIP01",
                            ["SOURCE_PORT"] = "HFF09AGM0100_UOP01",
                            ["FROM"] = "HFF09AGM0100,NULL,0201012",
                            ["TO"] = "HFF09MPI0200,HFF09MPI0200_LIP01,NULL",
                            ["TRANSPORT_JOB_TIMESTAMP"] = "2025-03-25T05:40:19.000Z",
                            ["FACTOR"] = 69.3,
                        }
                    };
                    dataMask["CARRIER"] = new List<DataMask>()
                    {
                        new DataMask
                        {
                            ["MAIN_CARR_ID"] = "2F02365",
                            ["SUB_CARR_ID"] = "2F02365,2F70671,2F28723",
                            ["CARR_SEQ"] = "3",
                            ["CARR_USE"] = "EMPTY",
                            ["CURRENTPORT"] = "HFF09CNV0300_ABP3003",
                            ["MOVESTATUS"] = "ARRIVED",
                            ["MOVEFLAG"] = "0",
                            ["PROD_DETAIL_CODE"] = "E3A",
                            ["ASSIGN_LOT_QTY"] = "0",
                            ["CARR_SIZE_TYPE"] = "STACK3",
                            ["ABNM_VALUE"] = "0",
                            ["LINE_ID"] = "FM0I",
                            ["TIMESTAMP"] = DateTime.Now,
                            ["GOOD_QTY"] = "0",
                            ["CURRENTLOCATION"] = "HFF09CNV0300",
                        }
                    };
                    dataMask["CARRIER"] = new List<DataMask>()
                    {
                        new DataMask
                        {
                            ["STOCKER_NAME"] = "HFF09AGN0300",
                            ["CAPACITY"] = "89.57",
                            ["MAXIMUM_CAPACITY"] = "834",
                            ["TRAY_CAPACITY"] = "83.25",
                            ["MAXIMUM_TRAY_CAPACITY"] = "2502",
                            ["RACK_LOAD_COUNT"] = "747",
                            ["RACK_EMPTY_COUNT"] = "87",
                            ["RESERVATED_RETURN_COUNT"] = "5",
                            ["TRAY_COUNT"] = "2083",
                            ["TRAY_REWORK_COUNT_AVG"] = "3",
                            ["TRAY_REWORK_COUNT_MAX"] = "153",
                            ["TRAY_REWORK_COUNT_MIN"] = "0",
                            ["RACK_DISABLE_COUNT"] = "4",
                            ["KOR_EQP_NAME"] = "상온Aging #03",
                            ["ENG_EQP_NAME"] = "상온Aging #03",
                            ["TIMESTAMP"] = DateTime.Now,
                            ["STEP"] = new List<DataMask>
                            {
                                new DataMask {
                                    ["STOCKER_NAME"] = "HFF09AGN0300",
                                    ["STEP_ID"] = "8106",
                                    ["RACK_STEP_COUNT"] = "88",
                                    ["TOTAL"] = "834",
                                    ["STEP_CAPACITY"] = "10.55",
                                },
                            }
                        }
                    };
                    break;
            }

            return dataMask;
        }

        [Test]
        public async UniTask ExecutePipeLine_AllTopics_RegistersAndHandlesMessages()
        {
            // Arrange - 파이프라인 설정
            foreach (var topic in topicNames)
            {
                // 필요한 UpdatedDataOnly 설정
                bool updatedDataOnly = topic != "ALL";

                var pipelineInfo = new MQTTPipeLineInfo(topic, updatedDataOnly)
                    .setDataMapper(new DataMapper(dataMasks[topic]))
                    .setHandler(handlers[topic].HandleData);

                pipeLine.Add(pipelineInfo);
            }
            Debug.Log("파이프라인 설정 완료.");
            // Act - 파이프라인 실행
            pipeLine.Execute();
            Debug.Log("파이프라인 Execute.");
            // Assert - 일정 시간 기다린 후 각 핸들러가 호출되었는지 확인
            await UniTask.Delay(1500);

            // 각 토픽별로 핸들러가 호출되었는지 확인
            foreach (var topic in topicNames)
            {
                Assert.IsTrue(handlers[topic].CallCount > 0, $"{topic} 토픽의 핸들러가 호출되지 않았습니다.");

                if (topic != "ALL")
                {
                    // ALL을 제외한 토픽은 ObjectIdKey가 설정되어 있어야 함
                    Assert.IsNotNull(handlers[topic].LastDataObject, $"{topic} 토픽의 핸들러에 전달된 DataObject가 null입니다.");
                }
            }
        }

        [Test]
        public async UniTask RemoveTopic_ShouldStopReceivingMessages()
        {
            // Arrange
            // AGV 토픽만 등록
            var agvInfo = new MQTTPipeLineInfo("AGV", true)
                .setDataMapper(new DataMapper(dataMasks["AGV"]))
                .setHandler(handlers["AGV"].HandleData);

            pipeLine.Add(agvInfo);
            pipeLine.Execute();

            // 메시지가 수신되도록 잠시 대기
            await UniTask.Delay(1000);

            // 초기 호출 횟수 저장
            int initialCallCount = handlers["AGV"].CallCount;
            Assert.IsTrue(initialCallCount > 0, "초기 AGV 토픽의 핸들러가 호출되지 않았습니다.");

            // Act
            pipeLine.Remove("AGV"); // AGV 토픽 제거

            // 핸들러 초기화
            handlers["AGV"].Reset();

            // 충분한 시간 대기
            await UniTask.Delay(1500);

            // Assert
            Assert.AreEqual(0, handlers["AGV"].CallCount, "토픽을 제거했지만 핸들러가 여전히 호출되고 있습니다.");
        }

        [Test]
        public async UniTask UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData()
        {
            // Arrange - 파이프라인 설정
            // TestMQTTPipeLine을 사용하여 직접 메시지를 보낼 수 있게 함
            var testPipeLine = new TestMQTTPipeLine();

            // UpdatedDataOnly가 true인 AGV 토픽 추가
            var agvInfo = new MQTTPipeLineInfo("AGV", true)
                .setDataMapper(new DataMapper(dataMasks["AGV"]))
                .setHandler(handlers["AGV"].HandleData);

            testPipeLine.Add(agvInfo);

            // Mock 데이터 생성 (업데이트가 있는 데이터)
            string jsonWithUpdates = "{\"VHL_NAME\":\"HFF09CNA8053\",\"AGV_IDX\":52,\"X\":223316,\"Y\":218171}";

            // Act
            // 첫 번째 메시지 전송 (초기 데이터)
            testPipeLine.TestOnTopicMessage("AGV", jsonWithUpdates);

            // 동일한 메시지 다시 전송 (변경 없음)
            testPipeLine.TestOnTopicMessage("AGV", jsonWithUpdates);

            // Assert
            // UpdatedDataOnly가 true이므로 두 번째 메시지는 핸들러를 호출하지 않아야 함
            // 실제로는 DataRepository가 필요하므로 완전한 테스트는 어려움
            // 여기서는 최소한 첫 번째 메시지가 처리되었는지 확인
            Assert.IsTrue(handlers["AGV"].CallCount > 0, "AGV 토픽의 핸들러가 호출되지 않았습니다.");
        }

        [Test]
        public void OnTopicMessage_ValidJsonObject_CallsHandler()
        {
            // Arrange
            var testPipeLine = new TestMQTTPipeLine();

            foreach (var topic in topicNames)
            {
                bool updatedDataOnly = topic != "ALL";
                var pipelineInfo = new MQTTPipeLineInfo(topic, updatedDataOnly)
                    .setDataMapper(new DataMapper(dataMasks[topic]))
                    .setHandler(handlers[topic].HandleData);

                testPipeLine.Add(pipelineInfo);
            }

            // Act
            // 각 토픽에 대해 유효한 JSON 메시지 전송
            testPipeLine.TestOnTopicMessage("AGV", "{\"VHL_NAME\":\"HFF09CNA8053\",\"AGV_IDX\":52}");
            testPipeLine.TestOnTopicMessage("CARRIER", "{\"MAIN_CARR_ID\":\"2F02365\",\"SUB_CARR_ID\":\"2F02365,2F70671,2F28723\"}");
            testPipeLine.TestOnTopicMessage("STOCKER_STACK", "{\"STOCKER_NAME\":\"HFF09AGN0300\",\"CAPACITY\":\"89.57\"}");
            testPipeLine.TestOnTopicMessage("ALL", "{\"key\":\"value\"}");

            // Assert
            // 각 토픽의 핸들러가 호출되었는지 확인
            foreach (var topic in topicNames)
            {
                Assert.AreEqual(1, handlers[topic].CallCount, $"{topic} 토픽의 핸들러가 정확히 한 번 호출되어야 합니다.");
            }
        }

        [Test]
        public void OnTopicMessage_JsonArray_CallsHandler()
        {
            // Arrange
            var testPipeLine = new TestMQTTPipeLine();

            var pipelineInfo = new MQTTPipeLineInfo("AGV", true)
                .setDataMapper(new DataMapper(dataMasks["AGV"]))
                .setHandler(handlers["AGV"].HandleData);

            testPipeLine.Add(pipelineInfo);

            // JSON 배열 메시지 생성
            string jsonArrayMessage =
                "[{\"VHL_NAME\":\"HFF09CNA8053\",\"AGV_IDX\":52},{\"VHL_NAME\":\"HFF09CNA8033\",\"AGV_IDX\":32}]";

            // Act
            testPipeLine.TestOnTopicMessage("AGV", jsonArrayMessage);

            // Assert
            Assert.AreEqual(1, handlers["AGV"].CallCount, "AGV 토픽의 핸들러가 정확히 한 번 호출되어야 합니다.");
            Assert.IsNotNull(handlers["AGV"].LastDataObject, "핸들러에 전달된 DataObject가 null입니다.");
        }

        [Test]
        public void OnTopicMessage_EmptyMessage_DoesNotCallHandler()
        {
            // Arrange
            var testPipeLine = new TestMQTTPipeLine();

            var pipelineInfo = new MQTTPipeLineInfo("AGV", true)
                .setDataMapper(new DataMapper(dataMasks["AGV"]))
                .setHandler(handlers["AGV"].HandleData);

            testPipeLine.Add(pipelineInfo);

            // Act
            testPipeLine.TestOnTopicMessage("AGV", ""); // 빈 메시지 전송

            // Assert
            Assert.AreEqual(0, handlers["AGV"].CallCount, "빈 메시지가 전달되었을 때 핸들러가 호출되지 않아야 합니다.");
        }

        [Test]
        public void OnTopicMessage_InvalidJson_DoesNotCallHandler()
        {
            // Arrange
            var testPipeLine = new TestMQTTPipeLine();

            var pipelineInfo = new MQTTPipeLineInfo("AGV", true)
                .setDataMapper(new DataMapper(dataMasks["AGV"]))
                .setHandler(handlers["AGV"].HandleData);

            testPipeLine.Add(pipelineInfo);

            // Act - 잘못된 JSON 형식의 메시지 전송
            testPipeLine.TestOnTopicMessage("AGV", "{invalid json}");

            // Assert
            Assert.AreEqual(0, handlers["AGV"].CallCount, "잘못된 JSON 형식의 메시지가 전달되었을 때 핸들러가 호출되지 않아야 합니다.");
        }

        [Test]
        public async UniTask UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData()
        {
            // Arrange
            // 1. 데이터 핸들러를 통해 수신된 데이터 추적
            var handler = new UpdatedDataTrackingHandler();

            // 2. AGV 토픽을 위한 데이터 마스크 설정
            var dataMask = new DataMask();
            dataMask.ObjectName = "AGV";
            dataMask.ObjectIdKey = "VHL_NAME";

            // 3. MQTTPipeLine 설정 (MockMQTTService 사용)
            var pipeline = new MQTTPipeLine("localhost", 1883);
            pipeline.UseMockup = true; // MockMQTTService 사용 설정

            // 4. UpdatedDataOnly=true로 토픽 등록
            var pipelineInfo = new MQTTPipeLineInfo("AGV", true)
                .setDataMapper(new DataMapper(dataMask))
                .setHandler(handler.HandleData);

            pipeline.Add(pipelineInfo);

            // Act
            // 파이프라인 실행 - 이것이 MockMQTTService를 통해 메시지를 보내기 시작
            pipeline.Execute();

            // 첫 번째 데이터 세트가 수신될 때까지 대기
            await UniTask.Delay(1500);

            // 첫 번째 데이터 세트의 콜백 수를 기록
            int initialCallCount = handler.CallCount;
            Assert.IsTrue(initialCallCount > 0, "첫 번째 메시지 세트가 수신되지 않았습니다.");

            // AGV 항목 수 저장
            int initialAgvCount = handler.ReceivedAgvItems.Count;
            Assert.IsTrue(initialAgvCount > 0, "첫 번째 메시지에 AGV 항목이 없습니다.");

            // 첫 번째 콜백에서 수신된 업데이트 항목 수 기록
            int firstUpdatedCount = handler.LastUpdatedCount;
            Assert.IsTrue(firstUpdatedCount > 0, "첫 번째 메시지에 업데이트된 데이터가 없습니다.");

            // 다음 데이터 세트가 도착하기를 기다림
            await UniTask.Delay(3000);

            // 두 번째 메시지가 도착했는지 확인
            int finalCallCount = handler.CallCount;
            Assert.IsTrue(finalCallCount > initialCallCount, "두 번째 메시지 세트가 수신되지 않았습니다.");

            // Assert
            // 기본 검사: 모든 호출에서 업데이트된 데이터만 전송되었는지 확인
            foreach (int updatedCount in handler.UpdatedCounts)
            {
                Assert.IsTrue(updatedCount > 0, "업데이트된 데이터가 없는 콜백이 있습니다.");
            }

            // 두 번째 메시지에서 처음 도착한 AGV 항목과 추가/변경된 항목이 있는지 확인
            // MockMQTTService는 매번 다른 데이터 세트를 보내므로 이런 차이가 있어야 함
            bool hasChanges = handler.ReceivedAgvItems.Count > initialAgvCount ||
                               handler.HasUpdatedExistingItems;

            Assert.IsTrue(hasChanges, "두 번째 메시지에서 변경된 데이터가 감지되지 않았습니다.");

            // 정리
            pipeline.Stop();
            pipeline.Dispose();
        }

        // UpdatedDataOnly 테스트를 위한 특수 핸들러
        public class UpdatedDataTrackingHandler
        {
            public int CallCount { get; private set; } = 0;
            public List<int> UpdatedCounts { get; private set; } = new List<int>();
            public int LastUpdatedCount { get; private set; } = 0;
            public HashSet<string> ReceivedAgvItems { get; private set; } = new HashSet<string>();
            public bool HasUpdatedExistingItems { get; private set; } = false;

            public void HandleData(IDataObject? dataObject)
            {
                CallCount++;
                ULog.Debug($"UpdatedDataTrackingHandler 호출됨. CallCount: {CallCount}");
                if (dataObject != null)
                {
                    // 업데이트 개수 기록
                    LastUpdatedCount = dataObject.UpdatedCount;
                    UpdatedCounts.Add(dataObject.UpdatedCount);

                    // AGV 데이터 분석
                    if (dataObject is DataArray agvData)
                    {
                        foreach (var item in agvData)
                        {
                            if (item is DataObject obj && obj.Name == "AGV" && obj["VHL_NAME"] != null)
                            {
                                string vhlName = obj["VHL_NAME"].ToString();

                                // 이미 존재하는 항목이 업데이트된 경우
                                if (ReceivedAgvItems.Contains(vhlName))
                                {
                                    HasUpdatedExistingItems = true;
                                }
                                // 새로운 항목 추가
                                ReceivedAgvItems.Add(vhlName);
                            }
                        }
                    }
                }
            }
        }
    }

    // MQTTPipeLine의 OnTopicMessage 메서드를 테스트하기 위한 확장 클래스
    public class TestMQTTPipeLine : MQTTPipeLine
    {
        public TestMQTTPipeLine() : base("localhost", 1883)
        {
            UseMockup = true;
        }

        public void TestOnTopicMessage(string topic, string message)
        {
            // private 메서드에 접근하기 위한 래퍼
            typeof(MQTTPipeLine).GetMethod("OnTopicMessage",
                System.Reflection.BindingFlags.NonPublic |
                System.Reflection.BindingFlags.Instance)?.Invoke(this, new object[] { topic, message });
        }
    }

    // 테스트용 데이터 핸들러 클래스
    public class TestDataHandler
    {
        public int CallCount { get; private set; } = 0;
        public IDataObject? LastDataObject { get; private set; } = null;

        public void HandleData(IDataObject? dataObject)
        {
            CallCount++;
            LastDataObject = dataObject;
        }

        public void Reset()
        {
            CallCount = 0;
            LastDataObject = null;
        }
    }
}