using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Dapper;
using MySqlConnector;
using Newtonsoft.Json;
using CncModels.Entity;
using log4net;

namespace CncCollector.Core
{
    /// <summary>
    /// 采集分析引擎。
    /// 在每次采集周期后，对比每台机床的当前数据与上一次采集数据，
    /// 生成分析记录（log_collect_analysis）和周期汇总（log_collect_cycle）。
    /// 异常类分析自动写入告警表（cnc_alert）。
    /// </summary>
    public class AnalysisEngine
    {
        private static readonly ILog _log = LogManager.GetLogger(typeof(AnalysisEngine));

        /// <summary>业务库连接字符串（写告警用）</summary>
        private readonly string _businessConnStr;

        /// <summary>日志库连接字符串（写分析/周期表用）</summary>
        private readonly string _logConnStr;

        /// <summary>内存缓存：machineId → 上一次采集状态快照</summary>
        private readonly ConcurrentDictionary<int, MachineSnapshot> _lastSnapshot = new ConcurrentDictionary<int, MachineSnapshot>();

        /// <summary>
        /// 采集缓存快照（每台机床的上一次状态）
        /// </summary>
        public class MachineSnapshot
        {
            public string ProgramName { get; set; }
            public decimal? PartCount { get; set; }
            public string DeviceStatus { get; set; }
            public DateTime CollectTime { get; set; }
        }

        /// <summary>
        /// 初始化分析引擎
        /// </summary>
        /// <param name="businessConnStr">业务库连接字符串</param>
        /// <param name="logConnStr">日志库连接字符串</param>
        public AnalysisEngine(string businessConnStr, string logConnStr)
        {
            _businessConnStr = businessConnStr ?? throw new ArgumentNullException(nameof(businessConnStr));
            _logConnStr = logConnStr ?? throw new ArgumentNullException(nameof(logConnStr));
        }

        /// <summary>
        /// 分析一次采集周期的所有设备数据，写入分析记录和周期汇总。
        /// </summary>
        /// <param name="rawLogId">本次原始日志ID（log_collect_raw.id）</param>
        /// <param name="collectAddressId">采集地址ID</param>
        /// <param name="addressName">采集地址名称</param>
        /// <param name="records">本次采集的结构化记录列表</param>
        /// <param name="machineDict">device_code → Machine 的查找字典</param>
        /// <param name="cycleStartTime">周期开始时间</param>
        /// <param name="durationMs">本次采集耗时（毫秒）</param>
        public void AnalyzeAndRecord(long rawLogId, int collectAddressId, string addressName,
            List<CollectRecord> records, Dictionary<string, Machine> machineDict,
            DateTime cycleStartTime, long durationMs)
        {
            if (records == null || records.Count == 0) return;

            try
            {
                var analysisTime = DateTime.Now;
                var hasAnomaly = false;
                var changeDistribution = new Dictionary<string, int>();
                int successCount = 0;

                // 构建 machineId → Machine 查找字典
                var machineById = new Dictionary<int, Machine>();
                foreach (var m in machineDict.Values)
                {
                    machineById[m.Id] = m;
                }

                // 逐条分析
                foreach (var rec in records)
                {
                    try
                    {
                        // 获取机床信息
                        Machine machine = null;
                        machineById.TryGetValue(rec.MachineId, out machine);
                        string machineName = machine?.Name ?? ("机床" + rec.MachineId);

                        // 当前值
                        string currentProgram = rec.ProgramName;
                        decimal? currentPartCount = rec.PartCount;
                        string currentStatus = rec.DeviceStatus;

                        // 获取上次快照
                        MachineSnapshot prev;
                        _lastSnapshot.TryGetValue(rec.MachineId, out prev);

                        // 计算分析类型和摘要
                        string analysisType;
                        string summary;
                        bool needAlert = false;
                        string alertType = null;

                        DetermineAnalysis(prev, currentProgram, currentPartCount, currentStatus,
                            machineName, out analysisType, out summary, out needAlert, out alertType);

                        // 计算变化量
                        decimal? partCountDelta = null;
                        if (currentPartCount.HasValue && prev != null && prev.PartCount.HasValue)
                        {
                            partCountDelta = currentPartCount.Value - prev.PartCount.Value;
                        }

                        // 构建分析明细JSON
                        var detail = new
                        {
                            previous = prev != null ? new
                            {
                                program = prev.ProgramName,
                                partCount = prev.PartCount,
                                status = prev.DeviceStatus
                            } : null,
                            current = new
                            {
                                program = currentProgram,
                                partCount = currentPartCount,
                                status = currentStatus
                            },
                            delta = new { partCount = partCountDelta },
                            collectTime = rec.CollectTime.ToString("yyyy-MM-dd HH:mm:ss")
                        };
                        string detailJson = JsonConvert.SerializeObject(detail);

                        // 写入分析记录
                        WriteAnalysis(new CncModels.Entity.CollectAnalysis
                        {
                            AnalysisTime = analysisTime,
                            RawLogId = rawLogId,
                            CollectAddressId = collectAddressId,
                            MachineId = rec.MachineId,
                            AnalysisType = analysisType,
                            PreviousProgram = prev?.ProgramName,
                            CurrentProgram = currentProgram,
                            PreviousPartCount = prev?.PartCount,
                            CurrentPartCount = currentPartCount,
                            PartCountDelta = partCountDelta,
                            PreviousStatus = prev?.DeviceStatus,
                            CurrentStatus = currentStatus,
                            AnalysisSummary = summary,
                            AnalysisDetail = detailJson
                        });

                        // 更新快照
                        _lastSnapshot[rec.MachineId] = new MachineSnapshot
                        {
                            ProgramName = currentProgram,
                            PartCount = currentPartCount,
                            DeviceStatus = currentStatus,
                            CollectTime = rec.CollectTime
                        };

                        // 统计分布
                        if (changeDistribution.ContainsKey(analysisType))
                            changeDistribution[analysisType]++;
                        else
                            changeDistribution[analysisType] = 1;

                        // 异常告警
                        if (needAlert)
                        {
                            hasAnomaly = true;
                            WriteAlert(alertType, rec.MachineId, collectAddressId, summary, detailJson);
                        }

                        // 统计成功数（非异常即为成功）
                        if (analysisType != "COLLECTION_FAILED" && analysisType != "DATA_ANOMALY")
                            successCount++;
                    }
                    catch (Exception ex)
                    {
                        _log.Error($"分析单条记录失败（machineId={rec.MachineId}）", ex);
                    }
                }

                // 写入周期汇总
                WriteCycleSummary(new CncModels.Entity.CollectCycle
                {
                    CycleTime = cycleStartTime,
                    CollectAddressId = collectAddressId,
                    RawLogId = rawLogId,
                    EndTime = analysisTime,
                    DurationMs = (int)durationMs,
                    TotalMachines = records.Count,
                    SuccessCount = successCount,
                    FailCount = records.Count - successCount,
                    ChangeDistribution = JsonConvert.SerializeObject(changeDistribution),
                    HasAnomaly = hasAnomaly ? 1 : 0,
                    CycleSummary = $"共{records.Count}台机床完成分析" + (hasAnomaly ? "，存在异常" : "")
                });
            }
            catch (Exception ex)
            {
                _log.Error($"采集分析失败（地址={addressName}, rawLogId={rawLogId}）", ex);
            }
        }

        /// <summary>
        /// 根据前后状态对比，确定分析类型
        /// </summary>
        private void DetermineAnalysis(MachineSnapshot prev, string currentProgram, decimal? currentPartCount,
            string currentStatus, string machineName, out string analysisType, out string summary,
            out bool needAlert, out string alertType)
        {
            needAlert = false;
            alertType = null;

            // 无历史快照 → 首次上线
            if (prev == null)
            {
                analysisType = "DEVICE_ONLINE";
                summary = $"机床{machineName}首次上线，程序={currentProgram ?? "未知"}";
                needAlert = true;
                alertType = "unknown_device";
                return;
            }

            string prevProgram = prev.ProgramName;
            decimal? prevPartCount = prev.PartCount;
            string prevStatus = prev.DeviceStatus;

            // 检测程序切换
            if (!string.IsNullOrEmpty(currentProgram) && !string.IsNullOrEmpty(prevProgram) &&
                !string.Equals(prevProgram, currentProgram, StringComparison.OrdinalIgnoreCase))
            {
                analysisType = "PROGRAM_SWITCH";
                summary = $"机床{machineName}程序切换: {prevProgram} → {currentProgram}";
                return;
            }

            // 检测手动清零（同程序下零件数下降）
            if (currentPartCount.HasValue && prevPartCount.HasValue &&
                currentPartCount.Value < prevPartCount.Value)
            {
                analysisType = "MANUAL_RESET";
                summary = $"机床{machineName}零件计数手动清零: {prevPartCount} → {currentPartCount}";
                return;
            }

            // 检测零件数增加
            if (currentPartCount.HasValue && prevPartCount.HasValue &&
                currentPartCount.Value > prevPartCount.Value)
            {
                decimal delta = currentPartCount.Value - prevPartCount.Value;
                analysisType = "PART_COUNT_INCREASE";
                summary = $"机床{machineName}新增{delta}个零件（{prevPartCount} → {currentPartCount}）";
                return;
            }

            // 检测设备离线/告警
            if (!string.IsNullOrEmpty(currentStatus) &&
                (currentStatus.Equals("OFFLINE", StringComparison.OrdinalIgnoreCase) ||
                 currentStatus.Equals("ALARM", StringComparison.OrdinalIgnoreCase) ||
                 currentStatus.Equals("EMERGENCY", StringComparison.OrdinalIgnoreCase)))
            {
                analysisType = "DEVICE_OFFLINE";
                summary = $"机床{machineName}设备离线/告警: {currentStatus}";
                needAlert = true;
                alertType = "device_offline";
                return;
            }

            // 检测数据异常（关键字段缺失但设备应该在线）
            if (string.IsNullOrEmpty(currentProgram) && !string.IsNullOrEmpty(currentStatus) &&
                !currentStatus.Equals("OFFLINE", StringComparison.OrdinalIgnoreCase))
            {
                analysisType = "DATA_ANOMALY";
                summary = $"机床{machineName}数据异常: 缺少程序名字段";
                needAlert = true;
                alertType = "data_anomaly";
                return;
            }

            // 无重大变化
            analysisType = "NORMAL_UNCHANGED";
            summary = $"机床{machineName}数据无重大变化";
        }

        /// <summary>
        /// 写入单条分析记录到 log_collect_analysis
        /// </summary>
        private void WriteAnalysis(CncModels.Entity.CollectAnalysis entity)
        {
            try
            {
                using (var conn = new MySqlConnection(_logConnStr))
                {
                    conn.Execute(@"INSERT INTO log_collect_analysis
                        (analysis_time, raw_log_id, collect_address_id, machine_id, analysis_type,
                         previous_program, current_program, previous_part_count, current_part_count,
                         part_count_delta, previous_status, current_status, analysis_summary,
                         analysis_detail, created_at)
                        VALUES (@AnalysisTime, @RawLogId, @CollectAddressId, @MachineId, @AnalysisType,
                                @PreviousProgram, @CurrentProgram, @PreviousPartCount, @CurrentPartCount,
                                @PartCountDelta, @PreviousStatus, @CurrentStatus, @AnalysisSummary,
                                @AnalysisDetail, NOW())",
                        new
                        {
                            entity.AnalysisTime,
                            entity.RawLogId,
                            entity.CollectAddressId,
                            entity.MachineId,
                            entity.AnalysisType,
                            entity.PreviousProgram,
                            entity.CurrentProgram,
                            entity.PreviousPartCount,
                            entity.CurrentPartCount,
                            entity.PartCountDelta,
                            entity.PreviousStatus,
                            entity.CurrentStatus,
                            entity.AnalysisSummary,
                            entity.AnalysisDetail
                        });
                }
            }
            catch (Exception ex)
            {
                _log.Error($"写入分析记录失败（machineId={entity.MachineId}）", ex);
            }
        }

        /// <summary>
        /// 写入周期汇总到 log_collect_cycle
        /// </summary>
        private void WriteCycleSummary(CncModels.Entity.CollectCycle entity)
        {
            try
            {
                using (var conn = new MySqlConnection(_logConnStr))
                {
                    conn.Execute(@"INSERT INTO log_collect_cycle
                        (cycle_time, collect_address_id, raw_log_id, end_time, duration_ms,
                         total_machines, success_count, fail_count, change_distribution,
                         has_anomaly, cycle_summary, created_at)
                        VALUES (@CycleTime, @CollectAddressId, @RawLogId, @EndTime, @DurationMs,
                                @TotalMachines, @SuccessCount, @FailCount, @ChangeDistribution,
                                @HasAnomaly, @CycleSummary, NOW())",
                        new
                        {
                            entity.CycleTime,
                            entity.CollectAddressId,
                            entity.RawLogId,
                            entity.EndTime,
                            entity.DurationMs,
                            entity.TotalMachines,
                            entity.SuccessCount,
                            entity.FailCount,
                            entity.ChangeDistribution,
                            entity.HasAnomaly,
                            entity.CycleSummary
                        });
                }
            }
            catch (Exception ex)
            {
                _log.Error("写入周期汇总失败", ex);
            }
        }

        /// <summary>
        /// 写入告警到 cnc_alert（业务库）
        /// </summary>
        private void WriteAlert(string alertType, int machineId, int collectAddressId, string title, string detail)
        {
            try
            {
                using (var conn = new MySqlConnection(_businessConnStr))
                {
                    conn.Execute(@"INSERT INTO cnc_alert (alert_type, machine_id, collect_address_id, title, detail, is_resolved, created_at)
                        VALUES (@AlertType, @MachineId, @AddressId, @Title, @Detail, 0, NOW())",
                        new
                        {
                            AlertType = alertType,
                            MachineId = machineId,
                            AddressId = collectAddressId,
                            Title = title,
                            Detail = detail
                        });
                }
            }
            catch (Exception ex)
            {
                _log.Error($"写入告警失败（alertType={alertType}, machineId={machineId}）", ex);
            }
        }
    }
}
