using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using System;
using System.ComponentModel;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;

namespace LFP_Manager.Threads
{
    class csDbThread
    {
        #region DELEGATE
        public delegate void UpdateAlarmHistory(DataTable aAlarmHistoryData);
        #endregion

        #region VARIABLES

        CommConfig Config = new CommConfig();
        CsDeviceData DeviceData;

        CsDeviceData OldDevData;

        DataTable CurrAlarmHistory;

        private Thread _dbProc;                      // ← 생성자에서 만들지 않음
        private volatile bool _dbThreadEnd = true;
        private volatile bool Active = false;

        private readonly object _stateLock = new object();
        private readonly ISynchronizeInvoke _ui; // 보통 Form 또는 Control

        private int mQty = 1;

        public event EventHandler<PrintEventArgs> OnPrint;
        public event UpdateAlarmHistory OnUpdateAlarmHistory = null;

        #endregion

        #region CONSTRUCTORS

        public csDbThread(CommConfig aConfig,
                            ISynchronizeInvoke uiInvoker,
                            CsDeviceData aModuleData)
        {
            Config = aConfig;
            DeviceData = aModuleData;
            OldDevData = new CsDeviceData();

            _ui = uiInvoker;
        }

        public void Dispose()
        {
            Stop();
        }

        public bool Start(CommConfig aConfig)
        {
            if (_dbProc != null && _dbProc.IsAlive) return false;

            Config = aConfig;
            mQty = (Config.CommType == csConstData.CommType.COMM_RS485) ? Config.ModuleQty : 1;

            if (_dbProc != null && _dbProc.IsAlive)
                return true; // 이미 동작 중

            _dbThreadEnd = false;
            Active = true;

            // DB 경로 준비 (예외는 루프에서 잡히지만 시작 시도 전 준비)
            try
            {
                csDbUtils.DbCreateLOG(Config);
                string modelname = csConstData.UART_MODEL[Config.UartModelIndex];

                // 최초 활성 진입
                if (Active)
                {
                    // START 정보 기록
                    _ = BmsAlarmDataInsert(modelname, (int)DbConstData.Alarm.START, (int)DbConstData.Flag.INFORMATION, DeviceData.ModuleData[0], DateTime.Now);
                }
            }
            catch (Exception ex)
            {
                RaiseOnPrint($"DB init error: {ex.Message}");
            }

            _dbProc = new Thread(dbThreadProcess)
            {
                IsBackground = true,
                Name = "DbThread"
            };
            _dbProc.Start();
            return true;
        }

        public void Stop()
        {
            Active = false;
            _dbThreadEnd = true;

            var t = _dbProc;
            if (t != null && t.IsAlive)
            {
                for (int i = 0; i < 5 && t.IsAlive; i++)
                    t.Join(200); // 최대 약 1초
            }
            _dbProc = null;

            // ★ 종료 직전 1회 저장/알람 기록
            FlushNow("Stop");
        }

        #endregion

        #region SAFETY RAISE HELPER
        private void RaiseOnUpdateAlarmHistory(DataTable dt)
        {
            if (_ui != null && _ui.InvokeRequired)
                _ui.BeginInvoke(new Action(() => OnUpdateAlarmHistory?.Invoke(dt)), null);
            else
                OnUpdateAlarmHistory?.Invoke(dt);
        }

        private void RaiseOnPrint(string msg)
        {
            var handler = OnPrint;
            if (handler == null) return;

            var args = new PrintEventArgs(0, msg);

            if (_ui != null && _ui.InvokeRequired)
            {
                try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
                catch { }
            }
            else
            {
                handler(this, args);
            }
        }
        #endregion

        #region DB THREAD

        private void dbThreadProcess()
        {
            int periodSec = Math.Max(1, Config.DbLogPeriod); // 0 방지
            string modelName = csConstData.UART_MODEL[Config.UartModelIndex];

            int lastLoggedSecond = -1;   // ★ 마지막으로 저장한 "초"

            try
            {
                CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(Config, DateTime.Now, "");
                RaiseOnUpdateAlarmHistory(CurrAlarmHistory); // UI 마샬링 버전
            }
            catch (Exception ex)
            {
                csLog.SystemDbErrorLog(Config, ex.Message, DateTime.Now, Application.ExecutablePath);
            }

            while (!_dbThreadEnd)
            {
                if (Active)
                {
                    DateTime nowLocal = DateTime.Now;
                    DateTime nowUtc = DateTime.UtcNow;

                    int sec = nowUtc.Second;

                    // “초 변경” + “주기 정합” 검사
                    // ★ 경과 시간 기반 주기 체크 (초 단위 주기 모두 정확)
                    if ((sec % periodSec) == 0 && sec != lastLoggedSecond)
                    {
                        // Database Log Process
                        try
                        {
                            CsDeviceData snapshot;
                            lock (_stateLock) { snapshot = DeviceData.DeepCopy(); }

                            int count = Math.Max(1, mQty);
                            for (int i = 0; i < count; i++)
                            {
                                // RS-485는 i+1, 단일 UART는 1
                                snapshot.ModuleData[i].mNo =
                                    (Config.CommType == csConstData.CommType.COMM_RS485) ? (i + 1) : 1;

                                csDbUtils.BmsLogDataInsert(modelName, ref snapshot.ModuleData[i], nowLocal, 1);
                            }
                        }
                        catch (Exception ex)
                        {
                            RaiseOnPrint($"DB loop error: {ex.Message}");
                        }
                        finally
                        {
                            // ★ 마지막 로그 시각 갱신 (드리프트 최소화를 원하면 lastLogUtc += TimeSpan.FromSeconds(periodSec))
                            lastLoggedSecond = sec;
                        }
                    }

                    try
                    {
                        // Alarm History Log
                        csDbUtils.DbCreateLOG(Config);

                        for (int i = 0; i < mQty; i++)
                        {
                            if (CheckStatusAndAlarm(i, modelName, Config, DeviceData.ModuleData[i], OldDevData.ModuleData[i]))
                            {
                                CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(Config, DateTime.Now, "");
                                RaiseOnUpdateAlarmHistory(CurrAlarmHistory);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        csLog.SystemDbErrorLog(Config, ex.Message, DateTime.Now, Application.ExecutablePath);
                    }
                }
                Thread.Sleep(100);
            }
        }

        #endregion

        #region BMS ALARM DB INSERT FUNCTION
        public static bool BmsAlarmDataInsert1(string modelname, CommConfig aConfig, int aAlarmCode, int aAlarmStatus, CsDeviceData.DeviceModuleData mData, DateTime dateTime)
        {
            bool resultFlag = false;
            string result = "";
            //public static string LogDbFileNameFormat = @"\{0}\{1}_{2}_LOG.DB";
            string dbFilename = string.Format(csDbUtils.LogDbFileNameFormat
                , String.Format("{0:yyMM}", DateTime.Now)
                , String.Format("{0:yyMMdd}", DateTime.Now)
                , modelname
                );
            string dbFilePath = Path.GetDirectoryName(Application.ExecutablePath) + csDbUtils.LogDbFilePath + dbFilename;

            if (Directory.Exists(Path.GetDirectoryName(dbFilePath)) == false)
            {
                throw new Exception("No Log DB file path");
            }

            if (File.Exists(dbFilePath) == false)
            {
                throw new Exception(String.Format("No Log DB file - BmsAlarmDataInsertByModule ({0})", dbFilename));
            }

            // Open database
            string strConn = @"Data Source=" + dbFilePath;
            using (SQLiteConnection connection = new SQLiteConnection(strConn))
            {
                try
                {
                    connection.Open();
                    csDbUtils.BeginTran(connection);

                    // Insert System data
                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        //sSQL = "insert into TrendTable ( TrendStamp, TagName, TagValue) Values ( " + IntToStr(stamp) + "," + name + "," + value + ");";
                        command.CommandText = String.Format("INSERT INTO TAlarmHistory (");
                        command.CommandText += "create_date"                                // 0
                            + ", alarm_code"                                                // 1
                            + ", alarm_status"                                              // 2
                            + ", alarm_cname"                                               // 3
                            + ", alarm_sname"                                               // 4
                            + ", module_no"                                                 // 5
                            + ", ct_no"                                                     // 6
                            + ", alarm_param_1"                                             // 7
                            + ", alarm_param_2"                                             // 8
                            + ", alarm_param_3"                                             // 9
                            ;
                        command.CommandText = command.CommandText
                            + ")"
                            + " Values (";

                        int total = 10;

                        for (int i = 0; i < (total - 1); i++) command.CommandText += "?,";
                        command.CommandText += "?);";

                        SQLiteParameter[] p = new SQLiteParameter[total];

                        for (int i = 0; i < total; i++)
                        {
                            if (i == 0)
                            {
                                p[i] = new SQLiteParameter(DbType.DateTime);
                            }
                            else
                            {
                                p[i] = new SQLiteParameter();
                            }
                            command.Parameters.Add(p[i]);
                        }

                        int j = 0;
                        p[j++].Value = dateTime;                                                                                //  0 create_date
                        p[j++].Value = aAlarmCode;                                                                              //  1 alarm_code
                        p[j++].Value = aAlarmStatus;                                                                            //  2 alarm_status
                        p[j++].Value = DbConstData.GetAlarmName(aAlarmCode);                                                    //  3 alarm_cname
                        p[j++].Value = DbConstData.GetFlagName(aAlarmStatus);                                                   //  4 alarm_sname
                        p[j++].Value = mData.mNo;                                                                               //  5 module_no
                        switch (aAlarmCode)
                        {
                            case 0:     // Cell Under Voltage
                                p[j++].Value = mData.AvgData.minCellNum;                                                        //  6 ct_no
                                p[j++].Value = mData.AvgData.minCellVoltage;                                                    //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 1:     // Cell Over Voltage
                                p[j++].Value = mData.AvgData.maxCellNum;                                                        //  6 ct_no
                                p[j++].Value = mData.AvgData.maxCellVoltage;                                                    //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 2:     // Module Under Voltage
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.voltage;                                                         //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 3:     // Module Over Voltage
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.voltage;                                                         //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 4:     // Charge High Temperature
                                p[j++].Value = mData.AvgData.maxTempNum;                                                        //  6 ct_no
                                p[j++].Value = mData.AvgData.maxTemp;                                                           //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 5:     // Discharge High Temperature
                                p[j++].Value = mData.AvgData.maxTempNum;                                                        //  6 ct_no
                                p[j++].Value = mData.AvgData.maxTemp;                                                           //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 6:     // Charge Low Temperature
                                p[j++].Value = mData.AvgData.minTempNum;                                                        //  6 ct_no
                                p[j++].Value = mData.AvgData.minTemp;                                                           //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 7:     // Discharge Low Temperature
                                p[j++].Value = mData.AvgData.minTempNum;                                                        //  6 ct_no
                                p[j++].Value = mData.AvgData.minTemp;                                                           //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 8:     // Charge Over Current
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.current;                                                         //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 9:     // Discharge Over Current
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.current;                                                         //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 10:     // Low SOC
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.SOC;                                                             //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 11:     // Low SOH
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.SOH;                                                             //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 12:     // Cell Voltage Difference
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.AvgData.minCellVoltage;                                                    //  7 alarm_param_1
                                p[j++].Value = mData.AvgData.maxCellVoltage;                                                    //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 13:     // Short Circuit
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.voltage;                                                         //  7 alarm_param_1
                                p[j++].Value = mData.ValueData.current;                                                         //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 14:     // Module Comm Fail
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.voltage;                                                         //  7 alarm_param_1
                                p[j++].Value = mData.ValueData.current;                                                         //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;

                            case 15:     // Cell Voltage Measurement Error
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.AvgData.minCellVoltage;                                                    //  7 alarm_param_1
                                p[j++].Value = mData.AvgData.maxCellVoltage;                                                    //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 16:     // Temperature Measurement Error
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.AvgData.minTemp;                                                           //  7 alarm_param_1
                                p[j++].Value = mData.AvgData.maxTemp;                                                           //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 17:     // Battery Cell Unbalance
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.AvgData.minCellVoltage;                                                    //  7 alarm_param_1
                                p[j++].Value = mData.AvgData.maxCellVoltage;                                                    //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            case 18:     // Emergency Alarm
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.voltage;                                                         //  7 alarm_param_1
                                p[j++].Value = mData.ValueData.current;                                                         //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;

                            case 21:     // STS_STAND_BY
                            case 22:     // STS_CHARING
                            case 23:     // STS_DISCHARGING
                            case 24:     // STS_FLOAT_CHARGING
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = mData.ValueData.voltage;                                                         //  7 alarm_param_1
                                p[j++].Value = mData.ValueData.current;                                                         //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                            default:     // STS_UNKNOWN
                                p[j++].Value = 0;                                                                               //  6 ct_no
                                p[j++].Value = 0;                                                                               //  7 alarm_param_1
                                p[j++].Value = 0;                                                                               //  8 alarm_param_2
                                p[j++].Value = 0;                                                                               //  9 alarm_param_3
                                break;
                        }

                        command.ExecuteNonQuery();
                        csDbUtils.CommitTran(connection);

                        resultFlag = true;
                    }
                }
                catch (Exception e)
                {
                    result = e.Message;
                }
                finally
                {
                    if (connection.State == ConnectionState.Open)
                    {
                        connection.Close();
                    }
                    if (result != "")
                    {
                        throw new Exception(result);
                    }
                }
            }
            return resultFlag;
        }
        #endregion

        #region ALARM STATUS CHECK
        private bool CheckStatusAndAlarm(int mNo, string modelName, CommConfig aConfig, CsDeviceData.DeviceModuleData nModule, CsDeviceData.DeviceModuleData oModule)
        {
            bool eventFlag = false;

            if (nModule == null || oModule == null)
                return false;

            // 현재/이전 경고/보호/에러 비트
            bool[] wNew = csUtils.UInt16ToBitArray((ushort)nModule.StatusData.warning);
            bool[] pNew = csUtils.UInt16ToBitArray((ushort)nModule.StatusData.protect);
            bool[] eNew = csUtils.UInt16ToBitArray((ushort)nModule.StatusData.errorCode);

            bool[] wOld = csUtils.UInt16ToBitArray((ushort)oModule.StatusData.warning);
            bool[] pOld = csUtils.UInt16ToBitArray((ushort)oModule.StatusData.protect);
            bool[] eOld = csUtils.UInt16ToBitArray((ushort)oModule.StatusData.errorCode);

            var alarmData = new THistoryAlarmData();
            DateTime now = DateTime.Now;

            // 시스템 통신 상태 변화 기록
            if (oModule.CommFail != nModule.CommFail)
            {
                eventFlag |= BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.COMM_FAIL,
                    nModule.CommFail ? (int)DbConstData.Flag.WARNING : (int)DbConstData.Flag.RELEASE, nModule, now);
                oModule.CommFail = nModule.CommFail;
            }
            // 0..15 비트에 대한 경고/보호 엣지 검사
            for (int i = 0; i < 16; i++)
            {
                alarmData.newFdata = pNew[i];
                alarmData.oldFdata = pOld[i];
                alarmData.newWdata = wNew[i];
                alarmData.oldWdata = wOld[i];

                switch (i)
                {
                    case 0:  // Over Voltage        0X0001
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.OVER_VOLTAGE, nModule, now);
                        break;
                    case 1:  // Cell Over Voltage   0x0002
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CELL_OVER_VOLTAGE, nModule, now);
                        break;
                    case 2:  // Under Voltage       0x0004
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.UNDER_VOLTAGE, nModule, now);
                        break;
                    case 3:  // Cell Under Voltage  0x0008
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CELL_UNDER_VOLTAGE, nModule, now);
                        break;
                    case 4:  // Charge Over Current 0x0010
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CHARGE_OVER_CURRENT, nModule, now);
                        break;
                    case 5:  // Discharge Over Current  0x0020
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.DISCHARGE_OVER_CURRENT, nModule, now);
                        break;
                    case 6:  // Reserved  0x0040
                        break;
                    case 7:  // MOSFET Over Temperature 0x0080
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.MOSFET_OVER_TERMPERATURE, nModule, now);
                        break;
                    case 8:  // Charge Over Temperature 0x0100
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CHARGE_OVER_TEMPERATURE, nModule, now);
                        break;
                    case 9:  // Discharge Over Temperature 0x0200
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.DISCHARGE_OVER_TEMPERATURE, nModule, now);
                        break;
                    case 10:  // Charge Low Temperature 0x0400
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CHARGE_LOW_TEMPERATURE, nModule, now);
                        break;
                    case 11:  // Discharge Low Temperature 0x0800
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.DISCHARGE_LOW_TEMPERATURE, nModule, now);
                        break;
                    case 12:  // Low Capacity(SOC)  0x1000
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.LOW_SOC, nModule, now);
                        break;
                    case 13:  // Short Circuit      0x2000
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.SHORT_CIRCUIT, nModule, now);
                        break;
                    case 14:  // CB OFF             0x4000
                        eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CB_OFF, nModule, now);
                        break;
                }
            }
            // Update last alarm status
            oModule.StatusData.warning = nModule.StatusData.warning;
            oModule.StatusData.protect = nModule.StatusData.protect;

            // 0..15 비트에 대한 Error 엣지 검사
            for (int i = 0; i < 16; i++)
            {
                alarmData.newEdata = eNew[i];
                alarmData.oldEdata = eOld[i];

                switch (i)
                {
                    case 0:  // Voltage Measurement Error       0X0001
                        eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.VOLTAGE_MEASUREMENT_ERROR, nModule, now);
                        break;
                    case 1:  // Temperature Measurement Error   0x0002
                        eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.TEMPERATURE_MEASUREMENT_ERROR, nModule, now);
                        break;
                    case 4:  // Battery Cell Unbalance          0x0010
                        eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.BATTERY_CELL_UNBALANCE, nModule, now);
                        break;
                    case 5:  // Emergency Alarm                 0x0020
                        eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.EMERGENCY_ALARM, nModule, now);
                        break;
                    default:
                        // Reserved Error
                        break;
                }
            }
            // Update last alarm status
            oModule.StatusData.errorCode = nModule.StatusData.errorCode;

            // Check Status Change
            if (oModule.StatusData.status != nModule.StatusData.status)
            {
                switch (nModule.StatusData.status)
                {
                    case 0:     // STANDBY
                        eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_STAND_BY,
                                                                  (int)DbConstData.Flag.STS_CHANGED, nModule, now);
                        break;
                    case 1:     // CHARGING
                        eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_CHARGING,
                                                                  (int)DbConstData.Flag.STS_CHANGED, nModule, now);
                        break;
                    case 2:     // DISCHARGING
                        eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_DISCHARGING,
                                                                  (int)DbConstData.Flag.STS_CHANGED, nModule, now);
                        break;
                    case 4:     // PROTECT
                        eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_PROTECTED,
                                                                  (int)DbConstData.Flag.STS_CHANGED, nModule, now);
                        break;
                    default:     // UNKNOWN
                        eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_UNKNOWN,
                                                                  (int)DbConstData.Flag.STS_CHANGED, nModule, now);
                        break;
                }
                oModule.StatusData.status = nModule.StatusData.status;
            }

            if (eventFlag)
            {
                csDbUtils.BmsLogDataInsert(modelName, ref nModule, now, 1, 1);
            }
            return eventFlag;
        }

        private bool InsertAlarmHistory(string modelName, THistoryAlarmData AlarmData, int alarmCode, CsDeviceData.DeviceModuleData mData, DateTime dateTime)
        {
            bool resultFlag = false;

            // Check protection
            if (AlarmData.oldFdata != AlarmData.newFdata)
            {
                if (AlarmData.newFdata)
                {
                    // Faulty
                    resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.PROTECT, mData, dateTime);
                }
                else
                {
                    if (AlarmData.oldWdata != AlarmData.newWdata)
                    {
                        if (AlarmData.newWdata)
                        {
                            // Warning
                            resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.WARNING, mData, dateTime);
                        }
                        else
                        {
                            // Release
                            resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.RELEASE, mData, dateTime);
                        }
                    }
                    else
                    {
                        if (AlarmData.newWdata == false)
                        {
                            // Release
                            resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.RELEASE, mData, dateTime);
                        }
                    }
                }
            }
            else
            {
                if (AlarmData.oldWdata != AlarmData.newWdata)
                {
                    if (AlarmData.newWdata)
                    {
                        // Warning
                        resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.WARNING, mData, dateTime);
                        //IDInsert("", alarmCode, CsDbConstData.DB_ALARM.FLAG_WARNING);
                    }
                    else
                    {
                        if (AlarmData.newFdata == false)
                        {
                            // Release
                            resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.RELEASE, mData, dateTime);
                            //IDInsert("", alarmCode, CsDbConstData.DB_ALARM.FLAG_RELEASE);
                        }
                    }
                }
            }
            return resultFlag;
        }

        private bool InsertErrorHistory(string modelName, THistoryAlarmData AlarmData, int alarmCode, CsDeviceData.DeviceModuleData mData, DateTime dateTime)
        {
            if (AlarmData.oldEdata == AlarmData.newEdata) return false;

            int status = AlarmData.newEdata ? (int)DbConstData.Flag.ERROR
                                            : (int)DbConstData.Flag.RELEASE;

            return BmsAlarmDataInsert(modelName, alarmCode, status, mData, dateTime);
        }

        #endregion

        #region BMS ALARM DB INSERT FUNCTION (C# 7.3 compatible)
        public static bool BmsAlarmDataInsert(
            string modelname,
            int aAlarmCode,
            int aAlarmStatus,
            CsDeviceData.DeviceModuleData mData,
            DateTime dateTime)
        {
            // 파일명/경로 안전 조합
            string dbFilename = string.Format(
                csDbUtils.LogDbFileNameFormat,
                DateTime.Now.ToString("yyMM"),
                DateTime.Now.ToString("yyMMdd"),
                modelname);

            string exeDir = Path.GetDirectoryName(Application.ExecutablePath);
            if (string.IsNullOrEmpty(exeDir))
                exeDir = AppDomain.CurrentDomain.BaseDirectory;

            string logPath = (csDbUtils.LogDbFilePath ?? string.Empty)
                                .TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            string dbDir = csUtils.CombineSafe(exeDir, logPath);
            string dbFilePath = csUtils.CombineSafe(dbDir, dbFilename);

            if (!Directory.Exists(Path.GetDirectoryName(dbFilePath)))
                throw new Exception("No Log DB file path");

            if (!File.Exists(dbFilePath))
                throw new Exception(string.Format("No Log DB file - BmsAlarmDataInsertByModule ({0})", dbFilename));

            // INSERT 대상 컬럼(총 10개)
            // create_date, alarm_code, alarm_status, alarm_cname, alarm_sname,
            // module_no, ct_no, alarm_param_1, alarm_param_2, alarm_param_3
            const int total = 10;

            using (var connection = new SQLiteConnection(@"Data Source=" + dbFilePath))
            {
                connection.Open();
                // ★ 성능/동시성 PRAGMA 적용 (WAL + busy_timeout 등) — C# 7.3 호환
                csDbUtils.SqlitePragmaHelper.ApplyPragmas(connection, true); // forWriter: true

                using (var tran = connection.BeginTransaction())
                {
                    try
                    {
                        using (var command = connection.CreateCommand())
                        {
                            command.Transaction = tran;

                            command.CommandText =
                                "INSERT INTO TAlarmHistory (" +
                                "create_date, alarm_code, alarm_status, alarm_cname, alarm_sname, " +
                                "module_no, ct_no, alarm_param_1, alarm_param_2, alarm_param_3" +
                                ") VALUES (" + string.Join(",", Enumerable.Repeat("?", total)) + ");";

                            // 파라미터 생성
                            var p = new SQLiteParameter[total];
                            for (int i = 0; i < total; i++)
                            {
                                p[i] = (i == 0) ? new SQLiteParameter(DbType.DateTime) : new SQLiteParameter();
                                command.Parameters.Add(p[i]);
                            }

                            // 공통 메타
                            int j = 0;
                            p[j++].Value = string.Format("{0:yyyy-MM-dd HH:mm:ss}", dateTime);                              // 0 create_date
                            p[j++].Value = aAlarmCode;                            // 1 alarm_code
                            p[j++].Value = aAlarmStatus;                          // 2 alarm_status
                            p[j++].Value = DbConstData.GetAlarmName(aAlarmCode);  // 3 alarm_cname
                            p[j++].Value = DbConstData.GetFlagName(aAlarmStatus); // 4 alarm_sname
                            p[j++].Value = mData.mNo;                             // 5 module_no

                            // 알람별 세부 파라미터 계산
                            int ctNo = 0;
                            double param1 = 0, param2 = 0, param3 = 0;

                            switch (aAlarmCode)
                            {
                                case 0: // Cell Under Voltage
                                    ctNo = mData.AvgData.minCellNum;
                                    param1 = mData.AvgData.minCellVoltage; param2 = 0;
                                    break;
                                case 1: // Cell Over Voltage
                                    ctNo = mData.AvgData.maxCellNum;
                                    param1 = mData.AvgData.maxCellVoltage; param2 = 0;
                                    break;
                                case 2: // Module Under Voltage
                                case 3: // Module Over Voltage
                                    ctNo = 0;
                                    param1 = mData.ValueData.voltage; param2 = 0;
                                    break;
                                case 4: // Charge High Temperature
                                case 5: // Discharge High Temperature
                                    ctNo = mData.AvgData.maxTempNum;
                                    param1 = mData.AvgData.maxTemp; param2 = 0;
                                    break;
                                case 6: // Charge Low Temperature
                                case 7: // Discharge Low Temperature
                                    ctNo = mData.AvgData.minTempNum;
                                    param1 = mData.AvgData.minTemp; param2 = 0;
                                    break;
                                case 8: // Charge Over Current
                                case 9: // Discharge Over Current
                                    ctNo = 0;
                                    param1 = mData.ValueData.current; param2 = 0;
                                    break;
                                case 10: // Low SOC
                                    ctNo = 0;
                                    param1 = mData.ValueData.SOC; param2 = 0;
                                    break;
                                case 11: // Low SOH
                                    ctNo = 0;
                                    try
                                    {
                                        var sohProp = mData.ValueData.GetType().GetProperty("SOH");
                                        if (sohProp != null)
                                        {
                                            object v = sohProp.GetValue(mData.ValueData, null);
                                            param1 = Convert.ToDouble(v);
                                        }
                                        else
                                        {
                                            param1 = mData.ValueData.SOC;
                                        }
                                    }
                                    catch
                                    {
                                        param1 = mData.ValueData.SOC;
                                    }
                                    param2 = 0;
                                    break;
                                case 12: // Cell Voltage Difference
                                    ctNo = 0;
                                    param1 = mData.AvgData.minCellVoltage; param2 = mData.AvgData.maxCellVoltage;
                                    break;
                                case 13: // Short Circuit
                                    ctNo = 0;
                                    param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
                                    break;
                                case 14: // Module Comm Fail
                                    ctNo = 0;
                                    param1 = 0; param2 = 0;
                                    break;
                                case 15: // MOSFET Over Temerature
                                    ctNo = 0;
                                    param1 = mData.ValueData.MosTemperature; param2 = 0;
                                    break;
                                case 16: // CB Off
                                    ctNo = 0;
                                    param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
                                    break;
                                case 17: // Voltage Measurement Error
                                    ctNo = 0;
                                    param1 = mData.AvgData.minCellVoltage; param2 = mData.AvgData.maxCellVoltage;
                                    break;
                                case 18: // Temperature Measurement Error
                                    ctNo = 0;
                                    param1 = mData.AvgData.minTemp; param2 = mData.AvgData.maxTemp;
                                    break;
                                case 19: // Battery Cell Unbalance
                                    ctNo = 0;
                                    param1 = mData.AvgData.minCellVoltage; param2 = mData.AvgData.maxCellVoltage;
                                    break;
                                case 20: // Emergency Alarm
                                    ctNo = 0;
                                    param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
                                    break;

                                // 상태 코드(스냅샷 저장) — 오타 보정: CHARING → CHARGING
                                case 21: // STS_STAND_BY
                                case 22: // STS_CHARGING
                                case 23: // STS_DISCHARGING
                                case 24: // STS_FLOAT_CHARGING
                                case 25: // STS_PRE_CHARGING
                                    ctNo = 0;
                                    param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
                                    break;

                                default: // STS_UNKNOWN 등
                                    ctNo = 0; param1 = 0; param2 = 0; param3 = 0;
                                    break;
                            }

                            p[j++].Value = ctNo;    // 6 ct_no
                            p[j++].Value = param1;  // 7 alarm_param_1
                            p[j++].Value = param2;  // 8 alarm_param_2
                            p[j++].Value = param3;  // 9 alarm_param_3

                            command.ExecuteNonQuery();
                        }
                        tran.Commit();
                        return true;
                    }
                    catch
                    {
                        try { tran.Rollback(); } catch { /* ignore */ }
                        throw;
                    }
                    finally
                    {
                        if (connection.State == ConnectionState.Open)
                            connection.Close();
                    }
                }
            }
        }
        #endregion

        #region CREATE HISTORY DB TABLE
        public static void DbCreate(string modelName, DateTime aDateTime)
        {
            // 일자 기준 DB 파일명 (yyMM/yyMMdd)
            string dbFilename = string.Format(
                csDbUtils.LogDbFileNameFormat,
                aDateTime.ToString("yyMM"),
                aDateTime.ToString("yyMMdd"),
                modelName);

            // 실행파일 경로 + 로그 DB 상대경로 + 파일명 안전 결합
            string exeDir = Path.GetDirectoryName(Application.ExecutablePath);
            if (string.IsNullOrEmpty(exeDir))
                exeDir = AppDomain.CurrentDomain.BaseDirectory;

            string logPath = (csDbUtils.LogDbFilePath ?? string.Empty)
                                .TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            string dbDir = csUtils.CombineSafe(exeDir, logPath);
            string dbFilePath = csUtils.CombineSafe(dbDir, dbFilename);

            if (Directory.Exists(System.IO.Path.GetDirectoryName(dbFilePath)) == false)
                Directory.CreateDirectory(System.IO.Path.GetDirectoryName(dbFilePath));

            if (File.Exists(dbFilePath) == false)
                // Create database
                SQLiteConnection.CreateFile(dbFilePath);

            // Open database
            string strConn = @"Data Source=" + dbFilePath;
            using (var connection = new SQLiteConnection(strConn))
            {
                connection.Open();
                try
                {
                    // Create table
                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = DbConstData.Database.CreateTable;
                        command.ExecuteNonQuery();
                    }
                }
                catch (Exception)
                {
                }
                finally
                {
                    connection.Close();
                }
            }
        }
        #endregion

        #region FLUSH ALARM HISTORY
        // csDbThread 클래스 내부 어딘가(예: #region DB THREAD 아래)
        private void FlushNow(string reason = null)
        {
            try
            {
                if (Config == null) return;

                // 모델/수량 재확인
                var modelName = csConstData.UART_MODEL[Config.UartModelIndex];
                mQty = (Config.CommType == csConstData.CommType.COMM_RS485) ? Config.ModuleQty : 1;

                // 스냅샷 확보
                CsDeviceData snapshot;
                lock (_stateLock)
                {
                    snapshot = DeviceData != null ? DeviceData.DeepCopy() : null;
                }
                if (snapshot == null) return;

                var now = DateTime.Now;
                var anyAlarm = false;

                // 모듈 값 1회 저장
                int count = Math.Max(1, mQty);
                for (int i = 0; i < count; i++)
                {
                    // RS-485는 i+1, 단일 UART는 1
                    snapshot.ModuleData[i].mNo =
                        (Config.CommType == csConstData.CommType.COMM_RS485) ? (i + 1) : 1;

                    csDbUtils.BmsLogDataInsert(modelName, ref snapshot.ModuleData[i], now, 1);
                }

                // STOP 정보 기록
                anyAlarm |= BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STOP, (int)DbConstData.Flag.INFORMATION, snapshot.ModuleData[0], now);

                if (anyAlarm)
                {
                    CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(Config, DateTime.Now, "");
                    RaiseOnUpdateAlarmHistory(CurrAlarmHistory);
                }

                if (!string.IsNullOrEmpty(reason))
                    RaiseOnPrint($"DB flushed ({reason}).");
            }
            catch (Exception ex)
            {
                RaiseOnPrint($"Flush error{(reason != null ? "(" + reason + ")" : "")}: {ex.Message}");
                // 치명적 예외는 로깅만 하고 흐름 유지
                csLog.SystemDbErrorLog(Config, ex.Message, DateTime.Now, Application.ExecutablePath);
            }
        }
        #endregion
    }
}
