package org.dromara.rfid.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.rfid.domain.RfidDevice;
import org.dromara.rfid.domain.RfidLocation;
import org.dromara.rfid.domain.vo.DashboardVO;
import org.dromara.rfid.domain.vo.RfidDeviceVo;
import org.dromara.rfid.domain.vo.RfidLocationVo;
import org.dromara.rfid.domain.vo.RfidReadRecordVo;
import org.dromara.rfid.helper.RfidReadRecordTableHelper;
import org.dromara.rfid.mapper.RfidDeviceMapper;
import org.dromara.rfid.mapper.RfidLocationMapper;
import org.dromara.rfid.mapper.RfidReadRecordMapper;
import org.dromara.rfid.service.IDashboardService;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 看板数据服务实现类
 * <p>
 * 提供三个独立接口：
 * <ul>
 *     <li>实时统计接口（按秒刷新）：顶部统计 + 告警列表</li>
 *     <li>设备状态接口（定时刷新）：设备树 + 设备最新读取记录</li>
 *     <li>成功率趋势接口（按小时统计）：成功率折线图</li>
 * </ul>
 * </p>
 *
 * @author zch
 * @date 2025-11-26
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class DashboardServiceImpl implements IDashboardService {

    private final RfidDeviceMapper deviceMapper;
    private final RfidLocationMapper locationMapper;
    private final RfidReadRecordMapper readRecordMapper;

    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 【接口1】获取实时统计数据（按秒刷新）
     */
    @Override
    public DashboardVO.RealtimeStats getRealtimeStats(Integer alarmLimit) {
        DashboardVO.RealtimeStats realtimeStats = new DashboardVO.RealtimeStats();

        // 获取统计概览
        realtimeStats.setOverview(getOverview());

        // 获取告警统计列表
        realtimeStats.setAlarmStats(getAlarmStats(alarmLimit));

        return realtimeStats;
    }

    /**
     * 获取统计概览
     */
    @Override
    public DashboardVO.StatisticsOverview getOverview() {
        DashboardVO.StatisticsOverview overview = new DashboardVO.StatisticsOverview();

        // 统计设备总数
        Long deviceTotal = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
            .eq(RfidDevice::getIsMarked, "1"));
        overview.setDeviceTotal(deviceTotal);

        // 统计在线数量
        Long onlineCount = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
            .eq(RfidDevice::getIsMarked, "1")
            .eq(RfidDevice::getOnlineStatus, "1"));
        overview.setOnlineCount(onlineCount);

        // 统计离线数量
        Long offlineCount = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
            .eq(RfidDevice::getIsMarked, "1")
            .eq(RfidDevice::getOnlineStatus, "0"));
        overview.setOfflineCount(offlineCount);

        // 统计告警数量（当天告警设备数）
        Long alarmCount = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
            .eq(RfidDevice::getIsMarked, "1")
            .eq(RfidDevice::getAlarmStatus, "1"));
        overview.setAlarmCount(alarmCount);

        return overview;
    }

    /**
     * 获取位置树（含设备信息）
     * <p>
     * 返回完整的位置树结构，位置类型为 3 时表示设备节点，关联设备信息。
     * 不返回读取记录数据，具体记录由 WebSocket 实时推送。
     * </p>
     */
    @Override
    public List<DashboardVO.LocationTreeNode> getLocationTree() {
        // 1. 查询所有已标识的位置
        QueryWrapper<RfidLocation> locationWrapper = Wrappers.query(RfidLocation.class);
        locationWrapper.eq("t.is_marked", "1");
        locationWrapper.orderByAsc("t.parent_id", "t.id");
        List<RfidLocationVo> locations = locationMapper.selectCustomRfidLocationVoList(locationWrapper);

        if (CollUtil.isEmpty(locations)) {
            return Collections.emptyList();
        }

        // 2. 查询所有已标识的设备，构建 locationId -> 设备列表 的映射
        QueryWrapper<RfidDevice> deviceWrapper = Wrappers.query(RfidDevice.class);
        deviceWrapper.eq("t.is_marked", "1");
        List<RfidDeviceVo> devices = deviceMapper.selectCustomRfidDeviceVoList(deviceWrapper);

        Map<Long, List<RfidDeviceVo>> devicesByLocation = devices.stream()
            .filter(d -> d.getLocationId() != null)
            .collect(Collectors.groupingBy(RfidDeviceVo::getLocationId));

        // 3. 构建位置树节点
        List<DashboardVO.LocationTreeNode> allNodes = new ArrayList<>();
        Map<Long, DashboardVO.LocationTreeNode> nodeMap = new HashMap<>();

        for (RfidLocationVo loc : locations) {
            DashboardVO.LocationTreeNode node = new DashboardVO.LocationTreeNode();
            node.setId(loc.getId());
            node.setLocationCode(loc.getLocationCode());
            node.setLocationAlias(loc.getLocationAlias());
            node.setLocationType(loc.getLocationType());
            node.setParentId(loc.getParentId());
            node.setChildren(new ArrayList<>());

            // 如果是工位（locationType = 3），关联设备信息
            if ("3".equals(loc.getLocationType())) {
                List<RfidDeviceVo> locationDevices = devicesByLocation.get(loc.getId());
                if (CollUtil.isNotEmpty(locationDevices)) {
                    // 取第一个设备（通常一个工位对应一个设备）
                    RfidDeviceVo device = locationDevices.get(0);
                    node.setDeviceId(device.getId());
                    node.setDeviceCode(device.getDeviceCode());
                    node.setDeviceName(device.getDeviceName());
                    node.setOnlineStatus(device.getOnlineStatus());
                    node.setAlarmStatus(device.getAlarmStatus());
                }
            }

            allNodes.add(node);
            nodeMap.put(loc.getId(), node);
        }

        // 4. 构建树形结构
        List<DashboardVO.LocationTreeNode> rootNodes = new ArrayList<>();
        for (DashboardVO.LocationTreeNode node : allNodes) {
            Long parentId = node.getParentId();
            if (parentId == null || parentId == 0L) {
                // 顶级节点
                rootNodes.add(node);
            } else {
                // 挂载到父节点
                DashboardVO.LocationTreeNode parent = nodeMap.get(parentId);
                if (parent != null) {
                    parent.getChildren().add(node);
                } else {
                    // 父节点不存在，作为顶级节点处理
                    rootNodes.add(node);
                }
            }
        }

        return rootNodes;
    }

    /**
     * 获取成功率趋势
     * <p>
     * 按小时统计指定日期（今日/昨日）的读取成功率，用于前端折线图展示。
     * 这里会保证返回 0-23 点共 24 条数据，即使某些小时没有任何读取记录，也会返回该时间点（成功率为 null）。
     * </p>
     *
     * @param type 类型：today-今日（默认），yesterday-昨日
     * @return 24 小时的成功率趋势列表
     */
    @Override
    public List<DashboardVO.SuccessRateTrend> getSuccessRateTrends(String type) {
        LocalDate today = LocalDate.now();
        LocalDate yesterday = today.minusDays(1);

        Map<String, Double> todayRateMap = buildSuccessRateMap(today);
        Map<String, Double> yesterdayRateMap = buildSuccessRateMap(yesterday);

        List<DashboardVO.SuccessRateTrend> trends = new ArrayList<>();

        for (int hour = 0; hour < 24; hour++) {
            String timePoint = String.format("%02d:00", hour);
            DashboardVO.SuccessRateTrend trend = new DashboardVO.SuccessRateTrend();
            trend.setTimePoint(timePoint);
            trend.setSuccessRate(todayRateMap.getOrDefault(timePoint, null));
            trend.setYesterdaySuccessRate(yesterdayRateMap.getOrDefault(timePoint, null));
            trends.add(trend);
        }

        return trends;
    }

    private Map<String, Double> buildSuccessRateMap(LocalDate targetDate) {
        String tableName = RfidReadRecordTableHelper.getTableName(targetDate);
        String startTime = targetDate.atStartOfDay().format(TIME_FORMATTER);
        String endTime = targetDate.atTime(23, 59, 59).format(TIME_FORMATTER);

        List<Map<String, Object>> results;
        try {
            results = readRecordMapper.selectSuccessRateByHour(tableName, startTime, endTime);
        } catch (Exception e) {
            log.warn("查询成功率趋势失败，表可能不存在: {}", tableName);
            results = Collections.emptyList();
        }

        Map<String, Double> rateMap = new HashMap<>();
        if (CollUtil.isNotEmpty(results)) {
            for (Map<String, Object> row : results) {
                String timePoint = (String) row.get("timePoint");
                if (timePoint == null) {
                    continue;
                }
                Object rateObj = row.get("successRate");
                Double rate = rateObj != null ? Double.parseDouble(rateObj.toString()) : null;
                rateMap.put(timePoint, rate);
            }
        }
        return rateMap;
    }

    /**
     * 获取告警统计列表
     * <p>
     * 当 limit 为 null 时不限制数量，返回全部告警记录。
     * </p>
     */
    @Override
    public List<DashboardVO.AlarmStatVO> getAlarmStats(Integer limit) {
        // limit 为 null 时不限制数量
        String tableName = RfidReadRecordTableHelper.getTodayTableName();

        List<RfidReadRecordVo> alarmRecords;
        try {
            alarmRecords = readRecordMapper.selectAlarmRecordList(tableName, limit);
        } catch (Exception e) {
            log.warn("查询告警记录失败，表可能不存在: {}", tableName);
            alarmRecords = Collections.emptyList();
        }

        return alarmRecords.stream().map(record -> {
            DashboardVO.AlarmStatVO alarmStat = new DashboardVO.AlarmStatVO();
            alarmStat.setAlarmTime(record.getRecordTime() != null
                ? DateUtil.format(record.getRecordTime(), "MM-dd HH:mm")
                : null);
            alarmStat.setDeviceName(record.getDeviceName());
            alarmStat.setLocation(record.getLocationAlias());
            alarmStat.setAlarmLevel(record.getAlarmLevel());
            alarmStat.setAlarmAction(record.getAlarmAction());
            return alarmStat;
        }).collect(Collectors.toList());
    }

    /**
     * 获取完整看板数据
     */
    @Override
    public DashboardVO getDashboardData(Long locationId) {
        DashboardVO dashboard = new DashboardVO();
        dashboard.setOverview(getOverview());
        dashboard.setLocationTree(getLocationTree());
        dashboard.setSuccessRateTrends(getSuccessRateTrends("today"));
        dashboard.setAlarmStats(getAlarmStats(null));
        return dashboard;
    }
}
