using System.Collections.Generic;
using System.Linq;
using Dapper;
using CncModels.Entity;
using CncModels.Dto;
using CncModels.Dto.Machine;
using CncRepository.Base;
using CncRepository.Interface;

namespace CncRepository.Impl
{
    /// <summary>
    /// 机床实现
    /// </summary>
    public class MachineRepository : BusinessRepository, IMachineRepository
    {
        public MachineRepository(string connectionString) : base(connectionString) { }

        /// <summary>机床SELECT列映射模板（snake_case列名 → PascalCase属性名）</summary>
        /// <summary>在线判断SQL片段：last_ping_time在20秒内视为在线</summary>
        private const string OnlineExpr = "(CASE WHEN last_ping_time IS NOT NULL AND last_ping_time >= NOW() - INTERVAL 20 SECOND THEN 1 ELSE 0 END)";

        private const string SelectColumns = @"id as Id, device_code as DeviceCode, name as Name, workshop_id as WorkshopId, collect_address_id as CollectAddressId, ip_address as IpAddress, brand_id as BrandId, is_enabled as IsEnabled, {0} as IsOnline, last_ping_time as LastPingTime, last_ping_latency as LastPingLatency, last_collect_time as LastCollectTime, last_device_status as LastDeviceStatus, last_run_status as LastRunStatus, last_program_name as LastProgramName, last_part_count as LastPartCount, last_operate_mode as LastOperateMode, last_machining_status as LastMachiningStatus, created_at as CreatedAt, updated_at as UpdatedAt";

        public Machine GetById(int id)
        {
            using (var conn = CreateConnection())
            {
                var cols = string.Format(SelectColumns, OnlineExpr);
                var sql = $"SELECT {cols} FROM cnc_machine WHERE id = @Id";
                return conn.QuerySingleOrDefault<Machine>(sql, new { Id = id });
            }
        }

        public MachineDetailResponse GetDetailById(int id)
        {
            using (var conn = CreateConnection())
            {
                var sql = @"SELECT m.id as Id, m.device_code as DeviceCode, m.name as Name, 
                            m.workshop_id as WorkshopId, ws.name as WorkshopName, 
                            m.collect_address_id as CollectAddressId, 
                            m.brand_id as BrandId, b.brand_name as BrandName, 
                            m.ip_address as IpAddress, 
                            m.is_enabled as IsEnabled, 
                            (CASE WHEN m.last_ping_time IS NOT NULL AND m.last_ping_time >= NOW() - INTERVAL 20 SECOND THEN 1 ELSE 0 END) as IsOnline, 
                            m.last_ping_latency as LastPingLatency,
                            w.id as WorkerId, w.name as WorkerName, 
                            m.last_program_name as LastProgramName, m.last_collect_time as LastCollectTime
                            FROM cnc_machine m 
                            LEFT JOIN cnc_workshop ws ON m.workshop_id = ws.id
                            LEFT JOIN cnc_brand b ON m.brand_id = b.id
                            LEFT JOIN cnc_worker_machine wm ON m.id = wm.machine_id
                            LEFT JOIN cnc_worker w ON wm.worker_id = w.id
                            WHERE m.id = @Id";
                return conn.QuerySingleOrDefault<MachineDetailResponse>(sql, new { Id = id });
            }
        }

        public PagedResult<MachineListItem> GetList(MachineQuery query)
        {
            using (var conn = CreateConnection())
            {
                var where = " WHERE 1=1";
                var p = new DynamicParameters();
                if (!string.IsNullOrWhiteSpace(query.Keyword))
                {
                    where += " AND (m.name LIKE @Keyword OR m.device_code LIKE @Keyword)";
                    p.Add("Keyword", $"%{query.Keyword}%");
                }
                if (query.WorkshopId.HasValue)
                {
                    where += " AND m.workshop_id = @WorkshopId";
                    p.Add("WorkshopId", query.WorkshopId.Value);
                }
                if (query.IsOnline.HasValue)
                {
                    if (query.IsOnline.Value == 1)
                        where += " AND m.last_ping_time IS NOT NULL AND m.last_ping_time >= NOW() - INTERVAL 20 SECOND";
                    else
                        where += " AND (m.last_ping_time IS NULL OR m.last_ping_time < NOW() - INTERVAL 20 SECOND)";
                }
                if (query.BrandId.HasValue)
                {
                    where += " AND m.brand_id = @BrandId";
                    p.Add("BrandId", query.BrandId.Value);
                }
                var limit = query.PageSize;
                var offset = query.Offset;
                var sql = @"SELECT m.id as Id, m.device_code as DeviceCode, m.name as Name, m.workshop_id as WorkshopId, ws.name as WorkshopName, m.collect_address_id as CollectAddressId, m.brand_id as BrandId, b.brand_name as BrandName, m.ip_address as IpAddress, m.is_enabled as IsEnabled, 
                            (CASE WHEN m.last_ping_time IS NOT NULL AND m.last_ping_time >= NOW() - INTERVAL 20 SECOND THEN 1 ELSE 0 END) as IsOnline, 
                            m.last_ping_latency as LastPingLatency,
                            m.last_program_name as LastProgramName, m.last_collect_time as LastCollectTime, w.id as WorkerId, w.name as WorkerName 
                              FROM cnc_machine m 
                              LEFT JOIN cnc_workshop ws ON m.workshop_id = ws.id
                              LEFT JOIN cnc_brand b ON m.brand_id = b.id
                              LEFT JOIN cnc_worker_machine wm ON m.id = wm.machine_id
                              LEFT JOIN cnc_worker w ON wm.worker_id = w.id" + where + @" ORDER BY m.id DESC LIMIT @Limit OFFSET @Offset";
                p.Add("Limit", limit);
                p.Add("Offset", offset);
                var totalSql = @"SELECT COUNT(*) FROM cnc_machine m" + where + @"";
                var total = conn.ExecuteScalar<int>(totalSql, p);
                var items = conn.Query<MachineListItem>(sql, p).ToList();
                return new PagedResult<MachineListItem>
                {
                    Items = items,
                    Total = total,
                    Page = query.Page,
                    PageSize = limit
                };
            }
        }

        public int Create(Machine entity)
        {
            using (var conn = CreateConnection())
            {
                var sql = @"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) 
                            VALUES (@DeviceCode, @Name, @WorkshopId, @CollectAddressId, @IpAddress, @BrandId, @IsEnabled, @CreatedAt, @UpdatedAt); 
                          SELECT LAST_INSERT_ID();";
                return conn.QuerySingle<int>(sql, entity);
            }
        }

        public bool Update(Machine entity)
        {
            using (var conn = CreateConnection())
            {
                var sql = @"UPDATE cnc_machine SET device_code = @DeviceCode, name = @Name, workshop_id = @WorkshopId, collect_address_id = @CollectAddressId, ip_address = @IpAddress, brand_id = @BrandId, is_enabled = @IsEnabled, updated_at = @UpdatedAt, last_program_name = @LastProgramName, last_collect_time = @LastCollectTime, last_device_status = @LastDeviceStatus, last_run_status = @LastRunStatus, last_machining_status = @LastMachiningStatus WHERE id = @Id";
                return conn.Execute(sql, entity) > 0;
            }
        }

        public bool Delete(int id)
        {
            using (var conn = CreateConnection())
            {
                var sql = @"DELETE FROM cnc_machine WHERE id = @Id";
                return conn.Execute(sql, new { Id = id }) > 0;
            }
        }

        public int BatchDelete(List<int> ids)
        {
            if (ids == null || ids.Count == 0) return 0;
            using (var conn = CreateConnection())
            {
                var sql = @"DELETE FROM cnc_machine WHERE id IN @Ids";
                return conn.Execute(sql, new { Ids = ids });
            }
        }

        public bool ToggleEnabled(int id)
        {
            using (var conn = CreateConnection())
            {
                var sql = @"UPDATE cnc_machine SET is_enabled = CASE WHEN is_enabled = 1 THEN 0 ELSE 1 END, updated_at = NOW() WHERE id = @Id";
                return conn.Execute(sql, new { Id = id }) > 0;
            }
        }

        public Machine GetByDeviceCode(string deviceCode)
        {
            using (var conn = CreateConnection())
            {
                var cols = string.Format(SelectColumns, OnlineExpr);
                var sql = $"SELECT {cols} FROM cnc_machine WHERE device_code = @DeviceCode";
                return conn.QuerySingleOrDefault<Machine>(sql, new { DeviceCode = deviceCode });
            }
        }

        public List<Machine> GetEnabledByAddressId(int collectAddressId)
        {
            using (var conn = CreateConnection())
            {
                var cols = string.Format(SelectColumns, OnlineExpr);
                var sql = $"SELECT {cols} FROM cnc_machine WHERE collect_address_id = @CollectAddressId AND is_enabled = 1";
                return conn.Query<Machine>(sql, new { CollectAddressId = collectAddressId }).ToList();
            }
        }

        public List<Machine> GetEnabledOnline()
        {
            using (var conn = CreateConnection())
            {
                var cols = string.Format(SelectColumns, OnlineExpr);
                var sql = $"SELECT {cols} FROM cnc_machine WHERE is_enabled = 1 AND last_ping_time IS NOT NULL AND last_ping_time >= NOW() - INTERVAL 20 SECOND";
                return conn.Query<Machine>(sql).ToList();
            }
        }

        public void UpdateLastCollect(int id, Machine entity)
        {
            using (var conn = CreateConnection())
            {
                var sql = @"UPDATE cnc_machine SET last_collect_time = @LastCollectTime, last_program_name = @LastProgramName, last_machining_status = @LastMachiningStatus, updated_at = NOW() WHERE id = @Id";
                var param = new
                {
                    Id = id,
                    LastCollectTime = entity.LastCollectTime,
                    LastProgramName = entity.LastProgramName,
                    LastMachiningStatus = entity.LastMachiningStatus
                };
                conn.Execute(sql, param);
            }
        }

        public void SetCollectAddress(int machineId, int? collectAddressId)
        {
            using (var conn = CreateConnection())
            {
                conn.Execute("UPDATE cnc_machine SET collect_address_id = @AddressId, updated_at = NOW() WHERE id = @Id",
                    new { Id = machineId, AddressId = collectAddressId });
            }
        }

        /// <inheritdoc/>
        public MachineCollectRecordItem GetLatestCollectRecord(int machineId)
        {
            using (var conn = CreateConnection())
            {
                string sql = @"SELECT DATE_FORMAT(cr.collect_time, '%Y-%m-%d %H:%i:%s') AS CollectTime,
                               cr.program_name AS ProgramName, cr.part_count AS PartCount, cr.run_status AS RunStatus
                               FROM cnc_collect_record cr
                               WHERE cr.machine_id = @MachineId
                               ORDER BY cr.collect_time DESC LIMIT 1";
                return conn.QuerySingleOrDefault<MachineCollectRecordItem>(sql, new { MachineId = machineId });
            }
        }

        /// <inheritdoc/>
        public List<MachineTodayProdItem> GetTodayProduction(int machineId)
        {
            using (var conn = CreateConnection())
            {
                // 优先从已汇总的日产量表取
                string dpSql = @"SELECT program_name AS ProgramName, CAST(total_quantity AS SIGNED) AS Quantity,
                                 total_run_time AS RunTime, total_cutting_time AS CuttingTime
                                 FROM cnc_daily_production
                                 WHERE machine_id = @MachineId AND production_date = CURDATE()";
                var dpItems = conn.Query<MachineTodayProdItem>(dpSql, new { MachineId = machineId }).AsList();
                if (dpItems.Count > 0) return dpItems;

                // 没有汇总数据则从分段表实时计算
                string segSql = @"SELECT seg.program_name AS ProgramName,
                                  CAST(SUM(CASE WHEN seg.is_settled=1 THEN seg.quantity
                                      ELSE COALESCE(seg.end_part_count, seg.start_part_count) - seg.start_part_count END) AS SIGNED) AS Quantity,
                                  NULL AS RunTime, NULL AS CuttingTime
                                  FROM cnc_production_segment seg
                                  WHERE seg.machine_id = @MachineId AND seg.production_date = CURDATE()
                                  GROUP BY seg.program_name";
                return conn.Query<MachineTodayProdItem>(segSql, new { MachineId = machineId }).AsList();
            }
        }

        /// <inheritdoc/>
        public List<MachineTrendItem> GetProductionTrend(int machineId, int days = 7)
        {
            using (var conn = CreateConnection())
            {
                // 优先从日产量表取近N天数据
                string sql = @"SELECT DATE_FORMAT(dp.production_date, '%Y-%m-%d') AS Date,
                               CAST(SUM(dp.total_quantity) AS SIGNED) AS Quantity
                               FROM cnc_daily_production dp
                               WHERE dp.machine_id = @MachineId
                               AND dp.production_date >= DATE_SUB(CURDATE(), INTERVAL @Days DAY)
                               GROUP BY dp.production_date
                               ORDER BY dp.production_date";
                var items = conn.Query<MachineTrendItem>(sql, new { MachineId = machineId, Days = days - 1 }).AsList();
                if (items.Count > 0) return items;

                // 没有汇总数据则从分段表实时计算
                string segSql = @"SELECT DATE_FORMAT(seg.production_date, '%Y-%m-%d') AS Date,
                                  CAST(SUM(CASE WHEN seg.is_settled=1 THEN seg.quantity
                                      ELSE COALESCE(seg.end_part_count, seg.start_part_count) - seg.start_part_count END) AS SIGNED) AS Quantity
                                  FROM cnc_production_segment seg
                                  WHERE seg.machine_id = @MachineId
                                  AND seg.production_date >= DATE_SUB(CURDATE(), INTERVAL @Days DAY)
                                  GROUP BY seg.production_date
                                  ORDER BY seg.production_date";
                return conn.Query<MachineTrendItem>(segSql, new { MachineId = machineId, Days = days - 1 }).AsList();
            }
        }

        /// <inheritdoc/>
        public List<MachineCollectRecordItem> GetRecentCollectRecords(int machineId, int limit = 20)
        {
            using (var conn = CreateConnection())
            {
                string sql = @"SELECT DATE_FORMAT(cr.collect_time, '%Y-%m-%d %H:%i:%s') AS CollectTime,
                               cr.program_name AS ProgramName, cr.part_count AS PartCount, cr.run_status AS RunStatus
                               FROM cnc_collect_record cr
                               WHERE cr.machine_id = @MachineId
                               ORDER BY cr.collect_time DESC LIMIT @Limit";
                return conn.Query<MachineCollectRecordItem>(sql, new { MachineId = machineId, Limit = limit }).AsList();
            }
        }
    }
}
