using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using SnmpSharpNet;
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace LFP_Manager.Threads
{
    /// <summary>
    /// 향상된 SNMP 통신 스레드 클래스 - 안전하고 효율적인 리소스 관리
    /// </summary>
    public class CsSnmpThread124050 : IDisposable
    {
        #region CONSTANTS
        private const int MAX_COMM_TIMEOUT = 20;
        private const int SNMP_TIMEOUT_MS = 1000;
        private const int SNMP_RETRIES = 1;
        private const int SNMP_PORT = 161;
        private const int THREAD_SLEEP_MS = 50;
        private const int OPERATION_SLEEP_MS = 500;
        private const string DEFAULT_COMMUNITY = "admin";
        #endregion

        #region FIELDS
        private DeviceSystemData _systemData;
        private readonly CommConfig _config;
        private readonly string[] _oidList;
        private readonly object _lockObject = new object();

        private Task _snmpTask;
        private CancellationTokenSource _cancellationTokenSource;
        private volatile bool _disposed = false;

        private string _targetIP = "";
        private bool _autoSnmpTx = true;
        private int _oidIndex = 0;
        private int _commTimeOut = 0;

        // SNMP SET 관련 필드
        private volatile int _setSnmpMode = 0;
        private volatile int _setSnmpMode1 = 0;
        private volatile uint _setSnmpValue = 0;
        private volatile string _setSnmpStringData = "";

        // 이벤트 및 델리게이트
        public event DataUpdate OnUpdate;
        public event SetResult OnSetResult;
        #endregion

        #region CONSTRUCTORS
        public CsSnmpThread124050()
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _snmpTask = Task.Run(() => SnmpThreadFunction(_cancellationTokenSource.Token));
        }

        public CsSnmpThread124050(CommConfig config, ref DeviceSystemData systemData)
        {
            _config = config ?? throw new ArgumentNullException(nameof(config));
            _systemData = systemData ?? throw new ArgumentNullException(nameof(systemData));

            _oidList = CsSnmpConstData124050.SnmpOidInfo;
            if (_oidList == null || _oidList.Length == 0)
            {
                throw new InvalidOperationException("OID 목록이 비어있습니다.");
            }

            _cancellationTokenSource = new CancellationTokenSource();
            _snmpTask = Task.Run(() => SnmpThreadFunction(_cancellationTokenSource.Token));
        }
        #endregion

        #region PUBLIC METHODS
        /// <summary>
        /// SNMP 통신 시작
        /// </summary>
        public void Start(string ip, bool autoTx = true)
        {
            if (string.IsNullOrWhiteSpace(ip))
                throw new ArgumentException("IP 주소가 유효하지 않습니다.", nameof(ip));

            lock (_lockObject)
            {
                _targetIP = ip;
                _autoSnmpTx = autoTx;
            }
        }

        /// <summary>
        /// SNMP 통신 중지
        /// </summary>
        public void Stop()
        {
            lock (_lockObject)
            {
                _autoSnmpTx = false;
            }
        }

        /// <summary>
        /// 현재 상태 반환
        /// </summary>
        public bool IsRunning => _autoSnmpTx && !_disposed;

        /// <summary>
        /// 자동 전송 모드 설정
        /// </summary>
        public void SetAutoTx(bool autoTx)
        {
            lock (_lockObject)
            {
                _autoSnmpTx = autoTx;
            }
        }

        /// <summary>
        /// SNMP SET 명령 (정수 값)
        /// </summary>
        public void SetDataBySnmp(int mode, uint value)
        {
            lock (_lockObject)
            {
                _setSnmpMode = mode;
                _setSnmpValue = value;
            }
        }

        /// <summary>
        /// SNMP SET 명령 (문자열 값)
        /// </summary>
        public void SetDataBySnmp(int mode, string value)
        {
            lock (_lockObject)
            {
                _setSnmpMode1 = mode;
                _setSnmpStringData = value ?? "";
            }
        }

        /// <summary>
        /// 리소스 해제
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region PRIVATE METHODS
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed && disposing)
            {
                _cancellationTokenSource?.Cancel();

                try
                {
                    _snmpTask?.Wait(1000); // 5초 대기
                }
                catch (OperationCanceledException)
                {
                    // 정상적인 취소
                }
                catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerException is OperationCanceledException)
                {
                    // 정상적인 취소
                }

                _cancellationTokenSource?.Dispose();
                _disposed = true;
            }
        }

        /// <summary>
        /// 메인 SNMP 스레드 함수
        /// </summary>
        private async Task SnmpThreadFunction(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    if (string.IsNullOrEmpty(_targetIP))
                    {
                        await Task.Delay(OPERATION_SLEEP_MS, cancellationToken);
                        continue;
                    }

                    using (var target = CreateSnmpTarget())
                    {
                        await ProcessSnmpOperations(target, cancellationToken);
                    }

                    await Task.Delay(OPERATION_SLEEP_MS, cancellationToken);
                }
                catch (OperationCanceledException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    LogError($"SNMP 스레드 오류: {ex.Message}");
                    await Task.Delay(1000, cancellationToken);
                }
            }
        }

        /// <summary>
        /// SNMP 타겟 생성
        /// </summary>
        private UdpTarget CreateSnmpTarget()
        {
            var agent = new IpAddress(_targetIP);
            return new UdpTarget((IPAddress)agent, SNMP_PORT, SNMP_TIMEOUT_MS, SNMP_RETRIES);
        }

        /// <summary>
        /// SNMP 작업 처리
        /// </summary>
        private async Task ProcessSnmpOperations(UdpTarget target, CancellationToken cancellationToken)
        {
            // SET 명령 처리
            if (_setSnmpMode != 0)
            {
                await ExecuteSetCommand(target, _setSnmpMode, _setSnmpValue, _setSnmpStringData);
                _setSnmpMode = 0;
                return;
            }

            if (_setSnmpMode1 != 0)
            {
                await ExecuteSetCommand(target, _setSnmpMode1, _setSnmpValue, _setSnmpStringData);
                _setSnmpMode1 = 0;
                return;
            }

            // GET 명령 처리
            if (_autoSnmpTx)
            {
                await ExecuteGetOperations(target, cancellationToken);
            }
        }

        /// <summary>
        /// SNMP SET 명령 실행
        /// </summary>
        private async Task<bool> ExecuteSetCommand(UdpTarget target, int mode, uint value, string stringValue)
        {
            try
            {
                var setPdu = new Pdu(PduType.Set);

                switch (mode)
                {
                    case 1: // Reset Command
                        setPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.13.1.0"), new Integer32(Convert.ToInt32(value)));
                        break;
                    case 2: // Module Quantity Set
                        setPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.4.0"), new Integer32(Convert.ToInt32(value)));
                        break;
                    case 3: // Alarm Output Mode
                        setPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.5.0"), new Integer32(Convert.ToInt32(value)));
                        break;
                    case 4: // Manufacture Date
                        setPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.6.0"), new Integer32(Convert.ToInt32(value)));
                        break;
                    case 5: // BMS Serial Number
                        setPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.7.0"), new OctetString(stringValue));
                        break;
                    default:
                        LogError($"알 수 없는 SET 모드: {mode}");
                        return false;
                }

                var param = new AgentParameters(SnmpVersion.Ver1, new OctetString(DEFAULT_COMMUNITY));
                var result = await Task.Run(() => target.Request(setPdu, param) as SnmpV1Packet);

                return ProcessSetResult(result);
            }
            catch (Exception ex)
            {
                LogError($"SET 명령 실행 중 오류: {ex.Message}");
                OnSetResult?.Invoke($"SET 명령 실행 실패: {ex.Message}", true);
                return false;
            }
        }

        /// <summary>
        /// SET 결과 처리
        /// </summary>
        private bool ProcessSetResult(SnmpV1Packet result)
        {
            if (result == null)
            {
                LogError("SNMP 에이전트로부터 응답이 없습니다.");
                OnSetResult?.Invoke("SNMP 에이전트 응답 없음", true);
                return false;
            }

            if (result.Pdu.ErrorStatus != 0)
            {
                var errorMsg = $"SNMP 응답 오류: Error {result.Pdu.ErrorStatus} index {result.Pdu.ErrorIndex}";
                LogError(errorMsg);
                OnSetResult?.Invoke(errorMsg, true);
                return false;
            }

            var resultValue = result.Pdu.VbList[0].Value.ToString();
            var successMsg = $"SNMP SET 성공: {resultValue}";
            OnSetResult?.Invoke(successMsg, false);
            return true;
        }

        /// <summary>
        /// SNMP GET 작업 실행
        /// </summary>
        private async Task ExecuteGetOperations(UdpTarget target, CancellationToken cancellationToken)
        {
            while (_autoSnmpTx && !cancellationToken.IsCancellationRequested)
            {
                if (_setSnmpMode != 0 || _setSnmpMode1 != 0)
                    break;

                try
                {
                    var pdu = new Pdu(PduType.Get);
                    pdu.VbList.Add(GetCurrentOid());

                    var param = new AgentParameters(SnmpVersion.Ver1, new OctetString(DEFAULT_COMMUNITY));

                    LogTransaction($"SEND {target.Address}: OID [{pdu.VbList[0].Oid}]");

                    var result = await Task.Run(() => target.Request(pdu, param) as SnmpV1Packet);

                    if (await ProcessGetResult(result, target))
                    {
                        _commTimeOut = 0;
                        _systemData.CommFail = false;
                    }
                    else
                    {
                        HandleCommunicationTimeout();
                    }

                    MoveToNextOid();
                    await Task.Delay(THREAD_SLEEP_MS, cancellationToken);
                }
                catch (Exception ex)
                {
                    LogError($"GET 작업 중 오류: {ex.Message}");
                    HandleCommunicationTimeout();
                    break;
                }
            }

            UpdateSystemStatus();
        }

        /// <summary>
        /// GET 결과 처리
        /// </summary>
        private async Task<bool> ProcessGetResult(SnmpV1Packet result, UdpTarget target)
        {
            return await Task.Run(() =>
            {
                if (result == null)
                {
                    LogError("SNMP 에이전트로부터 응답이 없습니다.");
                    return false;
                }

                if (result.Pdu.ErrorStatus != 0)
                {
                    LogError($"SNMP 응답 오류: Error {result.Pdu.ErrorStatus} index {result.Pdu.ErrorIndex}");
                    return false;
                }

                LogTransaction($"RECV {target.Address}: OID [{result.Pdu.VbList[0].Oid}]: [{result.Pdu.VbList[0].Value}]");

                // OID 검증 및 데이터 처리
                var oidArray = (uint[])result.Pdu.VbList[0].Oid;
                if (IsValidTargetOid(oidArray))
                {
                    ProcessValidData(result, oidArray);
                    return true;
                }

                return false;
            });
        }

        /// <summary>
        /// 유효한 타겟 OID인지 확인
        /// </summary>
        private bool IsValidTargetOid(uint[] oidArray)
        {
            return oidArray.Length >= 8 &&
                   oidArray[0] == 1 && oidArray[1] == 3 && oidArray[2] == 6 &&
                   oidArray[3] == 1 && oidArray[4] == 2 && oidArray[5] == 1 && oidArray[6] == 15;
        }

        /// <summary>
        /// 유효한 데이터 처리
        /// </summary>
        private void ProcessValidData(SnmpV1Packet result, uint[] oidArray)
        {
            try
            {
                if (result.Pdu.VbList[0].Value.Type == AsnType.OCTETSTRING)
                {
                    var value = result.Pdu.VbList[0].Value.ToString();
                    csMakeDataFunction124050.SetSnmpData((int)oidArray[7], value, ref _systemData);
                }

                CalcAvgTemperatureModule(ref _systemData, (uint)_systemData.tempQty);
                CheckShelfCommFail(ref _systemData);

                OnUpdate?.Invoke(this, ref _systemData);
            }
            catch (Exception ex)
            {
                LogError($"데이터 처리 중 오류: {ex.Message}");
            }
        }

        /// <summary>
        /// 통신 타임아웃 처리
        /// </summary>
        private void HandleCommunicationTimeout()
        {
            _commTimeOut++;
            if (_commTimeOut >= MAX_COMM_TIMEOUT)
            {
                _systemData.CommFail = true;
                InitModuleData();
                _commTimeOut = MAX_COMM_TIMEOUT;
            }
        }

        /// <summary>
        /// 시스템 상태 업데이트
        /// </summary>
        private void UpdateSystemStatus()
        {
            CheckShelfCommFail(ref _systemData);
            OnUpdate?.Invoke(this, ref _systemData);
        }

        /// <summary>
        /// 현재 OID 반환
        /// </summary>
        private string GetCurrentOid()
        {
            return _oidList?[_oidIndex] ?? "";
        }

        /// <summary>
        /// 다음 OID로 이동
        /// </summary>
        private void MoveToNextOid()
        {
            _oidIndex = (_oidIndex + 1) % (_oidList?.Length ?? 1);
        }

        /// <summary>
        /// 모듈 데이터 초기화
        /// </summary>
        private void InitModuleData()
        {
            if (_systemData?.ValueData == null) return;

            _systemData.ValueData.voltageOfPack = 0;
            _systemData.ValueData.current = 0;
            _systemData.ValueData.averageCurrent = 0;
            _systemData.ValueData.rSOC = 0;
            _systemData.ValueData.stateOfHealth = 0;

            Array.Clear(_systemData.ValueData.CellVoltage, 0, _systemData.ValueData.CellVoltage.Length);
            Array.Clear(_systemData.ValueData.CellTemperature, 0, _systemData.ValueData.CellTemperature.Length);

            if (_systemData.AvgData != null)
            {
                _systemData.AvgData.avgCellVoltage = 0;
                _systemData.AvgData.diffCellVoltage = 0;
                _systemData.AvgData.maxCellVoltage = 0;
                _systemData.AvgData.minCellVoltage = 0;
                _systemData.AvgData.maxCellNum = 0;
                _systemData.AvgData.minCellNum = 0;
                _systemData.AvgData.avgTemp = 0;
                _systemData.AvgData.diffTemp = 0;
                _systemData.AvgData.maxTemp = 0;
                _systemData.AvgData.minTemp = 0;
                _systemData.AvgData.maxTempNum = 0;
                _systemData.AvgData.minTempNum = 0;
            }
        }

        /// <summary>
        /// 선반 통신 실패 확인
        /// </summary>
        private void CheckShelfCommFail(ref DeviceSystemData systemData)
        {
            systemData.ShelfCommFail = systemData.StatusData.batteryStatus == 0x0003;
        }

        /// <summary>
        /// 평균 온도 계산
        /// </summary>
        private static void CalcAvgTemperatureModule(ref DeviceSystemData systemData, uint tempSize)
        {
            if (systemData?.ValueData?.CellTemperature == null || systemData.AvgData == null)
                return;

            var temperatures = systemData.ValueData.CellTemperature;
            var validCount = Math.Min((int)tempSize, temperatures.Length);

            if (validCount == 0)
            {
                systemData.AvgData.avgTemp = 0;
                return;
            }

            var sum = 0;
            var max = new { value = temperatures[0], index = 0 };
            var min = new { value = temperatures[0], index = 0 };

            for (int i = 0; i < validCount; i++)
            {
                var temp = temperatures[i];
                sum += temp;

                if (temp > max.value)
                    max = new { value = temp, index = i };
                if (temp < min.value)
                    min = new { value = temp, index = i };
            }

            systemData.AvgData.avgTemp = (short)(sum / validCount);
            systemData.AvgData.maxTemp = max.value;
            systemData.AvgData.maxTempNum = max.index;
            systemData.AvgData.minTemp = min.value;
            systemData.AvgData.minTempNum = min.index;
        }

        /// <summary>
        /// 트랜잭션 로그
        /// </summary>
        private void LogTransaction(string message)
        {
            try
            {
                var logMessage = csLog.trx_msg_print(message, 1);
                OnSetResult?.Invoke(logMessage, false);
            }
            catch
            {
                // 로그 실패 시 무시
            }
        }

        /// <summary>
        /// 오류 로그
        /// </summary>
        private void LogError(string message)
        {
            try
            {
                var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                var logMessage = $"[{timestamp}] {message}";
                OnSetResult?.Invoke(logMessage, true);
            }
            catch
            {
                // 로그 실패 시 무시
            }
        }
        #endregion
    }
}