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

namespace LFP_Manager.Threads
{
    // 콜백 델리게이트
    public delegate void SetResult(string result, bool error);
    public delegate void DataUpdate(object sender, ref CsDeviceData.DeviceModuleData rData);

    class CsSnmpThread
    {
        #region VARIABLES (상태)
        private CsDeviceData.DeviceModuleData ModuleData;
        private CommConfig Config;

        // 스레드/토큰 기반 (Abort 제거)
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        private Task _snmpTask;

        // 상태 보호용 락
        private readonly object _stateLock = new object();

        // 실행 상태
        private bool snmpThreadHold = true;      // true = 정지, false = 동작
        private string targetIP = "";
        private bool AutoSnmpTx = true;

        // OID 순회
        private int OidIndex = 0;
        private int OidMax = 0;
        private string[] OidList = Array.Empty<string>();

        // 통신 타임아웃 카운터
        private int CommTimeOut = 0;

        // 결과 메시지 (레거시 호환)
        private bool snmpDataRecv = false;
        private string snmpResult = "";

        // 대기중 SET 명령 (race 방지 위해 락으로 보호)
        private int _pendingModeInt = 0;
        private int _pendingModeStr = 0;
        private UInt32 _pendingValue = 0;
        private string _pendingString = null;

        private long _lastUpdateTicks = 0;
        private const int UpdateMinIntervalMs = 120;

        // 이벤트
        public event DataUpdate OnUpdate = null;
        public event SetResult OnSetResult = null;
        #endregion

        #region CONSTRUCTORS

        public CsSnmpThread(CommConfig aConfig, ref CsDeviceData.DeviceModuleData mData)
        {
            Config = aConfig;
            ModuleData = mData;

            OidList = CsSnmpConstData.SnmpOidInfo ?? Array.Empty<string>();
            OidMax = OidList.Length;
            OidIndex = 0;

            _snmpTask = Task.Factory.StartNew(
                () => snmpThreadFuncA(_cts.Token),
                _cts.Token,
                TaskCreationOptions.LongRunning,
                TaskScheduler.Default);
        }
        #endregion

        #region PUBLIC API
        /// <summary>
        /// 안전 종료 (Abort 제거)
        /// </summary>
        public void disposeThread()
        {
            Stop();
            _cts.Cancel();
            try { _snmpTask?.Wait(1500); } catch { /* ignore */ }
        }

        /// <summary>
        /// 스레드 실행 시작/재시작
        /// </summary>
        public void Start(string IP, bool autoTx)
        {
            lock (_stateLock)
            {
                targetIP = IP ?? "";
                AutoSnmpTx = autoTx;
                snmpThreadHold = false;
            }
        }

        /// <summary>
        /// 스레드 일시정지
        /// </summary>
        public void Stop()
        {
            lock (_stateLock)
            {
                snmpThreadHold = true;
            }
        }

        /// <summary>
        /// true면 정지 상태
        /// </summary>
        public bool GetStatus()
        {
            lock (_stateLock) { return snmpThreadHold; }
        }

        /// <summary>
        /// 레거시: 마지막 결과 문자열 반환 후 플래그 리셋
        /// </summary>
        public string GetResult()
        {
            lock (_stateLock)
            {
                if (snmpDataRecv)
                {
                    snmpDataRecv = false;
                    return snmpResult;
                }
            }
            return "";
        }

        public void SetAutoTx(bool autoTx)
        {
            lock (_stateLock) { AutoSnmpTx = autoTx; }
        }

        public string GetSnmpOidInfo(int index)
        {
            if (index < 0 || index >= OidMax) return null;
            return OidList[index];
        }

        /// <summary>
        /// 정수형 SET 명령 예약
        /// </summary>
        public void SetDataBySnmp(int mode, UInt32 value)
        {
            lock (_stateLock)
            {
                _pendingModeInt = mode;
                _pendingValue = value;
            }
        }

        /// <summary>
        /// 문자열 SET 명령 예약
        /// </summary>
        public void SetDataBySnmp(int mode, string value)
        {
            lock (_stateLock)
            {
                _pendingModeStr = mode;
                _pendingString = value;
            }
        }
        #endregion

        #region COMM. THREAD

        private void snmpThreadFuncA(CancellationToken ct)
        {
            var wait = new ManualResetEventSlim(false);
            int backoffMs = 50; // 정상 루프 기본 지연

            while (!ct.IsCancellationRequested)
            {
                bool hold;
                string ip;
                bool autoTx;

                lock (_stateLock)
                {
                    hold = snmpThreadHold;
                    ip = targetIP;
                    autoTx = AutoSnmpTx;
                }

                if (hold || string.IsNullOrWhiteSpace(ip))
                {
                    // 대기
                    wait.Wait(TimeSpan.FromMilliseconds(500), ct);
                    continue;
                }

                // Target 생성 및 재사용 범위
                if (!TryParseIp(ip, out var addr))
                {
                    OnSetResultSafe($"Invalid IP address: {ip}", true);
                    wait.Wait(TimeSpan.FromMilliseconds(1000), ct);
                    continue;
                }

                var param = CreateAgentParams(); // 커뮤니티/버전 통일

                using (var target = new UdpTarget(addr, 161, 1000, 1))
                {
                    var pdu = new Pdu(PduType.Get);

                    while (!ct.IsCancellationRequested)
                    {
                        lock (_stateLock)
                        {
                            if (snmpThreadHold) break;
                        }

                        // 1) 대기중 SET 우선 처리
                        int modeInt, modeStr;
                        UInt32 setVal;
                        string setStr;

                        lock (_stateLock)
                        {
                            modeInt = _pendingModeInt;
                            modeStr = _pendingModeStr;
                            setVal = _pendingValue;
                            setStr = _pendingString;

                            _pendingModeInt = 0;
                            _pendingModeStr = 0;
                            _pendingString = null;
                        }

                        if (modeInt != 0 || modeStr != 0)
                        {
                            int mode = (modeInt != 0) ? modeInt : modeStr;
                            bool err = SetCmdbySnmp(target, mode, setVal, setStr);
                            backoffMs = err ? Math.Min(backoffMs * 2, 2000) : 100; // 에러 시 백오프
                            continue;
                        }

                        // 2) GET 폴링
                        autoTx = AutoSnmpTx;
                        if (!autoTx)
                        {
                            wait.Wait(TimeSpan.FromMilliseconds(200), ct);
                            continue;
                        }

                        if (OidMax == 0)
                        {
                            OnSetResultSafe("SNMP OID list is empty.", true);
                            wait.Wait(TimeSpan.FromMilliseconds(1000), ct);
                            continue;
                        }

                        int idx = NextOidIndex();
                        string oid = GetSnmpOidInfo(idx);
                        if (string.IsNullOrEmpty(oid))
                        {
                            Thread.Sleep(100);
                            continue;
                        }

                        pdu.VbList.Clear();
                        pdu.VbList.Add(oid);

                        try
                        {
                            target.Timeout = 500;

                            OnSetResultSafe(csLog.trx_msg_print(
                                $"SEND {target.Address}: OID [{pdu.VbList[0].Oid}]", 0), false);

                            var packet = (SnmpV1Packet)target.Request(pdu, param);
                            if (packet == null)
                            {
                                HandleNoResponse(ref backoffMs);
                                continue;
                            }

                            if (packet.Pdu.ErrorStatus != 0)
                            {
                                string oidTxt = (packet.Pdu.VbList.Count > 0)
                                    ? packet.Pdu.VbList[0].Oid.ToString()
                                    : "(no vb)";

                                OnSetResultSafe(csLog.trx_msg_print(
                                    $"Error in SNMP reply. Error {packet.Pdu.ErrorStatus} index {packet.Pdu.ErrorIndex} - {oidTxt}", 1), true);

                                HandleCommFailStep(ref backoffMs);
                                continue;
                            }

                            if (packet.Pdu.VbList.Count == 0)
                            {
                                OnSetResultSafe("SNMP reply has empty VbList.", true);
                                HandleCommFailStep(ref backoffMs);
                                continue;
                            }

                            var vb = packet.Pdu.VbList[0];

                            OnSetResultSafe(csLog.trx_msg_print(
                                $"RECV {target.Address}: OID [{vb.Oid}]: [{vb.Value}]", 1), false);

                            // 특정 트리만 반영 (1.3.6.1.2.1.15.*) → 필요에 맞게 수정하세요.
                            if (TryParseShelfOid(vb, out uint leaf))
                            {
                                if (vb.Value.Type == AsnType.OCTETSTRING)
                                {
                                    try
                                    {
                                        csMakeDataFunction.SetSnmpData((int)leaf, vb.Value.ToString(), ref ModuleData);
                                    }
                                    catch (Exception ex)
                                    {
                                        OnSetResultSafe($"{NowPrefix()} {ex.Message}", true);
                                    }
                                }

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

                                var nowTicks = Environment.TickCount;
                                if (nowTicks - _lastUpdateTicks >= UpdateMinIntervalMs)
                                {
                                    OnUpdate?.Invoke(this, ref ModuleData);
                                    _lastUpdateTicks = nowTicks;
                                }
                            }

                            CommTimeOut = 0;
                            ModuleData.CommFail = false;
                            backoffMs = 50;

                            Thread.Sleep(backoffMs);
                        }
                        catch (Exception ex)
                        {
                            string curOid = pdu.VbList.Count > 0 ? pdu.VbList[0].Oid.ToString() : "(no oid)";
                            OnSetResultSafe(csLog.trx_msg_print(
                                $"Exception in SNMP reply. OID {curOid}: {ex.Message}", 1), true);

                            HandleCommFailStep(ref backoffMs);
                        }
                    } // inner while
                } // using target

                Thread.Sleep(500);
            } // outer while
        }
        #endregion

        #region SNMP HELPERS
        private AgentParameters CreateAgentParams()
        {
            // 커뮤니티/버전 통일: v1 + "public" (필요시 Ver2로 변경)
            // Ver2가 가능하면: new AgentParameters(SnmpVersion.Ver2, new OctetString("public"));
            return new AgentParameters(SnmpVersion.Ver1, new OctetString("admin"));
        }

        private bool SetCmdbySnmp(UdpTarget sTarget, int mode, UInt32 value, string svalue)
        {
            bool resultError = false;
            var SetPdu = new Pdu(PduType.Set);
            SetPdu.VbList.Clear();

            // 모드별 OID 매핑 (기존 코드 유지)
            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(svalue ?? ""));
                    break;
                default:
                    OnSetResultSafe($"Unsupported SET mode: {mode}", true);
                    return true;
            }

            try
            {
                var aparam = CreateAgentParams();
                var result = sTarget.Request(SetPdu, aparam) as SnmpV1Packet;

                if (result == null)
                {
                    OnSetResultSafe($"{NowPrefix()} No response received from SNMP agent.", true);
                    return true;
                }

                if (result.Pdu.ErrorStatus != 0)
                {
                    string oidTxt = (result.Pdu.VbList.Count > 0)
                        ? result.Pdu.VbList[0].Oid.ToString()
                        : "(no vb)";

                    OnSetResultSafe($"Error in SNMP SET. Error {result.Pdu.ErrorStatus} index {result.Pdu.ErrorIndex} - {oidTxt}", true);
                    return true;
                }

                var vbOk = result.Pdu.VbList.Count > 0 ? result.Pdu.VbList[0] : null;
                var val = vbOk != null ? vbOk.Value.ToString() : "(no vb)";
                OnSetResultSafe($"SNMP SET OK. OID [{vbOk?.Oid}] -> [{val}]", false);
                return false;
            }
            catch (Exception ex)
            {
                OnSetResultSafe($"{NowPrefix()} Exception: {ex.Message}", true);
                resultError = true;
            }

            return resultError;
        }

        private static bool TryParseIp(string ip, out IPAddress addr)
        {
            if (IPAddress.TryParse(ip, out addr)) return true;
            try
            {
                var host = Dns.GetHostEntry(ip);
                addr = host?.AddressList?.FirstOrDefault(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
                return addr != null;
            }
            catch { addr = null; return false; }
        }

        private int NextOidIndex()
        {
            // 간단한 라운드로빈
            int next = Interlocked.Increment(ref OidIndex);
            if (OidMax == 0) return 0;
            next %= OidMax;
            if (next < 0) next += OidMax;
            return next;
        }

        private static bool TryParseShelfOid(Vb vb, out uint leaf)
        {
            leaf = 0;
            if (vb?.Oid == null) return false;

            // OID를 문자열로 받아 숫자 배열로 파싱
            // 예: ".1.3.6.1.2.1.15.7.0" 또는 "1.3.6.1.2.1.15.7.0"
            string oidStr = vb.Oid.ToString();
            if (string.IsNullOrWhiteSpace(oidStr)) return false;

            // 선행 '.' 제거 후 분해
            var parts = oidStr.Trim('.').Split('.');
            if (parts.Length < 8) return false;

            // 숫자 배열로 변환 (변환 실패 시 false)
            uint[] ids = new uint[parts.Length];
            for (int i = 0; i < parts.Length; i++)
            {
                if (!uint.TryParse(parts[i], out ids[i]))
                    return false;
            }

            // 예시 트리: 1.3.6.1.2.1.15.x.*
            if (ids[0] == 1 && ids[1] == 3 && ids[2] == 6 &&
                ids[3] == 1 && ids[4] == 2 && ids[5] == 1 &&
                ids[6] == 15)
            {
                leaf = ids[7];
                return true;
            }

            return false;
        }

        private void HandleNoResponse(ref int backoffMs)
        {
            lock (_stateLock)
            {
                if (!snmpDataRecv)
                {
                    snmpResult = $"{NowPrefix()} No response received from SNMP agent.";
                    snmpDataRecv = true;
                    OnSetResult?.Invoke(snmpResult, true);
                }
            }

            CommTimeOut++;
            if (CommTimeOut >= 10)
            {
                ModuleData.Reset();
                ModuleData.CommFail = true;

                var nowTicks = Environment.TickCount;
                if (nowTicks - _lastUpdateTicks >= UpdateMinIntervalMs)
                {
                    OnUpdate?.Invoke(this, ref ModuleData);
                    _lastUpdateTicks = nowTicks;
                }

                CommTimeOut = 10;
            }
            Thread.Sleep(backoffMs = Math.Min(backoffMs * 2, 2000));
        }

        private void HandleCommFailStep(ref int backoffMs)
        {
            CommTimeOut++;
            if (CommTimeOut >= 10)
            {
                ModuleData.Reset();
                ModuleData.CommFail = true;

                var nowTicks = Environment.TickCount;
                if (nowTicks - _lastUpdateTicks >= UpdateMinIntervalMs)
                {
                    OnUpdate?.Invoke(this, ref ModuleData);
                    _lastUpdateTicks = nowTicks;
                }

                CommTimeOut = 10;
            }
            Thread.Sleep(backoffMs = Math.Min(backoffMs * 2, 2000));
        }

        private void OnSetResultSafe(string msg, bool error)
        {
            try { OnSetResult?.Invoke(msg, error); } catch { /* swallow */ }
        }

        private static string NowPrefix() => DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ");
        #endregion

        #region DOMAIN HELPERS (기존 함수 보강/유지)
        private void CheckShelfCommFail(ref CsDeviceData.DeviceModuleData mData)
        {
            // 상태코드 0x0003 → ShelfCommFail=true (기존 가정 유지)
            if (mData.StatusData.batteryStatus == 0x0003) { mData.ShelfCommFail = true; }
            else { mData.ShelfCommFail = false; }
        }

        // 기존 코드 유지 (평균/최대/최소 계산)
        static void CalcAvgTemperatureModule(ref CsDeviceData.DeviceModuleData mData, UInt32 tSize)
        {
            short i, j;
            int sum;
            TMinMax min, max;

            min = new TMinMax();
            max = new TMinMax();

            sum = 0;
            max.value = 0;
            max.num = 0;
            min.value = 0;
            min.num = 0;

            j = 0;
            for (i = 0; i < tSize; i++)
            {
                if (j == 0)
                {
                    max.value = mData.ValueData.CellTemperature[i];
                    max.num = i;
                    min.value = mData.ValueData.CellTemperature[i];
                    min.num = i;
                }

                if (max.value < mData.ValueData.CellTemperature[i])
                {
                    max.value = mData.ValueData.CellTemperature[i];
                    max.num = i;
                }
                if (min.value > mData.ValueData.CellTemperature[i])
                {
                    min.value = mData.ValueData.CellTemperature[i];
                    min.num = i;
                }

                sum += mData.ValueData.CellTemperature[i];
                j++;
            }

            if (j == 0) { mData.AvgData.avgTemp = 0; }
            else { mData.AvgData.avgTemp = (short)(sum / j); }

            mData.AvgData.maxTemp = max.value;
            mData.AvgData.maxTempNum = max.num;
            mData.AvgData.minTemp = min.value;
            mData.AvgData.minTempNum = min.num;
        }
        #endregion
    }
}
