package com.haha.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haha.common.constant.DeviceConstants;
import com.haha.common.constant.OrderConstants;
import com.haha.common.enums.DeviceDoorStatus;
import com.haha.common.enums.OpenType;
import com.haha.common.enums.OrderStatus;
import com.haha.common.enums.PayStatus;
import com.haha.common.exception.BusinessException;
import com.haha.common.utils.EntityLabelUtils;
import com.haha.common.vo.DeviceStatisticsVO;
import com.haha.entity.Device;
import com.haha.entity.Order;
import com.haha.entity.Shop;
import com.haha.entity.User;
import com.haha.mapper.DeviceMapper;
import com.haha.mapper.ShopMapper;
import com.haha.service.DeviceService;
import com.haha.service.OrderService;
import com.haha.service.UserService;
import com.haha.service.DoorRecordService;
import com.haha.service.payment.payscore.PayScoreService;
import com.haha.common.utils.OrderUtils;
import com.haha.common.vo.OpenDoorVO;
import com.haha.sdk.HahaClient;
import com.haha.sdk.exception.HahaException;
import com.haha.sdk.model.DeviceOnlineStatus;
import com.haha.sdk.model.OpenDoorResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {

private final HahaClient hahaClient;
    private final OrderService orderService;
    private final ShopMapper shopMapper;
    private final DeviceMapper deviceMapper;
    private final UserService userService;
    private final DoorRecordService doorRecordService;

    @Autowired(required = false)
    private PayScoreService payScoreService;

    @Override
    public IPage<Device> getPage(int page, int pageSize, String deviceId, Long shopId, Integer status) {
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
        
        wrapper.like(deviceId != null && !deviceId.isEmpty(), Device::getDeviceId, deviceId)
               .eq(shopId != null, Device::getShopId, shopId)
               .eq(status != null, Device::getStatus, status)
               .orderByDesc(Device::getCreateTime);

        IPage<Device> devicePage = this.page(new Page<>(page, pageSize), wrapper);

        // 批量填充额外字段（避免 N+1 查询）
        fillDeviceLabelsBatch(devicePage.getRecords());

        return devicePage;
    }

    @Override
    public Device getDetailById(Long id) {
        Device device = this.getById(id);
        if (device == null) {
            return null;
        }
        fillDeviceLabels(device);
        return device;
    }

    @Override
    public DeviceStatisticsVO getStatistics() {
        // 总设备数
        long total = this.count();

        // 在线设备数
        long online = this.lambdaQuery()
                .eq(Device::getStatus, DeviceConstants.STATUS_ONLINE)
                .count();

        // 离线设备数
        long offline = total - online;

        return DeviceStatisticsVO.builder()
                .total(total)
                .online(online)
                .offline(offline)
                .totalSales("0.00")
                .todaySales("0.00")
                .avgTemperature("0.0")
                .build();
    }

    @Override
    public boolean openDoor(Long id, String doorIndex) {
        Device device = this.getById(id);
        if (device == null) {
            throw new BusinessException(404, "设备不存在");
        }
            
        log.info("远程开门：deviceId={}, doorIndex={}", device.getDeviceId(), doorIndex);
            
        try {
            // 调用 SDK 发送开门指令
            // 注意：管理后台开门默认为上货模式 (IN)
            // outUserId 必须是 6-32 位数字或字母，不能包含特殊字符
            String outUserId = "ADMIN" + System.currentTimeMillis(); // 生成临时用户编号（纯数字）
            
            // 检查是否多门单开设备
            String finalDoorIndex = null;
            Integer multiDoorType = hahaClient.getDeviceApi().isMultiDoorUnique(device.getDeviceId());
            
            // 如果是多门单开设备，使用传入的 doorIndex；否则不传递门索引
            if (multiDoorType != null && multiDoorType > 0) {
                finalDoorIndex = doorIndex != null ? doorIndex : DeviceConstants.DOOR_A;
                log.info("设备 {} 是多门单开设备，类型：{}，将打开 {} 门", device.getDeviceId(), multiDoorType, finalDoorIndex);
            } else {
                log.info("设备 {} 是单门设备或不支持多门单开，不传递门索引参数", device.getDeviceId());
            }
            
            OpenDoorResult result = hahaClient.getDeviceApi().openDoor(
                device.getDeviceId(), 
                outUserId,
                OpenType.IN.getCode(), // 管理后台开门为上货模式
                finalDoorIndex,
                "ADMIN"
            );
                
            log.info("开门成功 - 设备：{}, 活动 ID: {}, 用户 ID: {}", 
                    device.getDeviceId(), result.getActivityId(), result.getUserId());
            return true;
        } catch (Exception e) {
            log.error("开门失败 - 设备：{}, error: {}", device.getDeviceId(), e.getMessage());
            throw new BusinessException(500, "开门失败：" + e.getMessage());
        }
    }

    @Override
    public boolean setTemperature(Long id, Double temperature) {
        Device device = this.getById(id);
        if (device == null) {
            throw new BusinessException(404, "设备不存在");
        }
            
        log.info("设置温度：deviceId={}, temperature={}", device.getDeviceId(), temperature);
            
        try {
            // 调用 SDK 设置温度
            hahaClient.getDeviceApi().setTemperature(device.getDeviceId(), temperature);
                
            // 更新本地设备温度 (注意：temperature 字段是 String 类型)
            device.setTemperature(String.valueOf(temperature));
            device.setUpdateTime(LocalDateTime.now());
            this.updateById(device);
                
            log.info("温度设置成功 - 设备：{}, 温度：{}℃", device.getDeviceId(), temperature);
            return true;
        } catch (Exception e) {
            log.error("温度设置失败 - 设备：{}, error: {}", device.getDeviceId(), e.getMessage());
            throw new BusinessException(500, "温度设置失败:" + e.getMessage());
        }
    }

    @Override
    public boolean setVolume(Long id, Integer volume) {
        Device device = this.getById(id);
        if (device == null) {
            throw new BusinessException(404, "设备不存在");
        }
            
        log.info("设置音量：deviceId={}, volume={}", device.getDeviceId(), volume);
            
        try {
            // 调用 SDK 设置音量 (SDK 使用 voice 参数，范围 0-15)
            // 前端传入的 volume 范围是 0-100，需要转换
            int voiceLevel = (int) Math.round(volume / 100.0 * 15);
            voiceLevel = Math.max(0, Math.min(15, voiceLevel)); // 确保在 0-15 范围内
                
            hahaClient.getDeviceApi().setVoice(device.getDeviceId(), voiceLevel);
                
            // 更新本地设备音量
            device.setVolume(volume);
            device.setUpdateTime(LocalDateTime.now());
            this.updateById(device);
                
            log.info("音量设置成功 - 设备：{}, 音量值：{} (SDK 级别：{})", 
                    device.getDeviceId(), volume, voiceLevel);
            return true;
        } catch (Exception e) {
            log.error("音量设置失败 - 设备：{}, error: {}", device.getDeviceId(), e.getMessage());
            throw new BusinessException(500, "音量设置失败：" + e.getMessage());
        }
    }

    @Override
    public List<Device> getNearbyDevices(Double longitude, Double latitude, Double distance) {
        // 这里需要实现获取附近设备的逻辑，暂时返回所有设备
        return list();
    }

    @Override
    public Device getDeviceBySn(String deviceId) {
        return lambdaQuery().eq(Device::getDeviceId, deviceId).one();
    }

    @Override
    public boolean updateDeviceStatus(String deviceId, Integer status) {
        return lambdaUpdate().eq(Device::getDeviceId, deviceId).set(Device::getStatus, status).update();
    }

    @Override
    public List<Device> getDeviceListByArea(String province, String city, String district) {
        // 这里需要实现根据区域获取设备列表的逻辑，暂时返回所有设备
        return list();
    }

    @Override
    public boolean updateInventoryHash(String deviceId, String inventoryHash) {
        return lambdaUpdate().eq(Device::getDeviceId, deviceId).set(Device::getCurrentInventoryHash, inventoryHash).update();
    }

    @Override
    public OpenDoorVO scanOpenDoor(String deviceId, Long userId) throws HahaException {
        log.info("用户 {} 请求打开设备 {}", userId, deviceId);

        // 1. 检查设备在线状态
        DeviceOnlineStatus onlineStatus = hahaClient.getDeviceApi().getOnlineStatus(deviceId);
        if (onlineStatus.getIsOnline() != 1) {
            log.warn("设备 {} 当前离线", deviceId);
            throw new BusinessException("设备当前离线，请稍后再试");
        }

        // 2. 检查是否多门单开
        Integer multiDoorType = hahaClient.getDeviceApi().isMultiDoorUnique(deviceId);
        String doorIndex = null;

        // 如果是多门单开设备，默认打开A门
        if (multiDoorType != null && multiDoorType > 0) {
            doorIndex = DeviceConstants.DOOR_A;
            log.info("设备 {} 是多门单开设备，类型: {}，将打开A门", deviceId, multiDoorType);
        }

        // 3. 生成商户用户编号并开门
        String openType = OpenType.OUT.getCode();
        String source = "MINIAPP";

        OpenDoorResult openResult = hahaClient.getDeviceApi().openDoor(deviceId, String.valueOf(userId), openType, doorIndex, source);
        
        // 4. 【修改】保存开门记录到数据库和 Redis（不再立即创建订单）
        doorRecordService.saveDoorOpenRecord(
            deviceId,
            userId,
            openResult.getActivityId(),
            doorIndex,
            openType,
            source
        );

        // 4.1 将支付分预授权信息持久化到 DoorRecord，防止 Redis 过期后丢失
        persistPayScorePreAuth(deviceId, userId, openResult.getActivityId());

        // 5. 返回成功结果
        log.info("开门成功 - 设备：{}, 活动 ID: {}, 用户 ID: {}", deviceId, openResult.getActivityId(), openResult.getUserId());

        return OpenDoorVO.builder()
            .doorOpened(true)
            .activityId(openResult.getActivityId())
            .userId(openResult.getUserId())
            .deviceId(deviceId)
            .doorIndex(doorIndex != null ? doorIndex : DeviceConstants.DEFAULT_DOOR_INDEX)
            .build();
    }

    @Override
    public boolean linkToShop(Long deviceId, Long shopId) {
        Device device = this.getById(deviceId);
        if (device == null) {
            throw new BusinessException("设备不存在");
        }
        
        device.setShopId(shopId);
        device.setUpdateTime(LocalDateTime.now());
        
        boolean result = this.updateById(device);
        if (result) {
            log.info("设备关联门店成功: deviceId={}, shopId={}", deviceId, shopId);
        }
        return result;
    }

    @Override
    public int batchLinkToShop(List<Long> deviceIds, Long shopId) {
        int count = 0;
        for (Long deviceId : deviceIds) {
            try {
                if (linkToShop(deviceId, shopId)) {
                    count++;
                }
            } catch (Exception e) {
                log.error("设备关联门店失败: deviceId={}, error={}", deviceId, e.getMessage());
            }
        }
        log.info("批量关联门店完成: 总数={}, 成功={}", deviceIds.size(), count);
        return count;
    }

    @Override
    public List<Device> getUnlinkedDevices() {
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
        wrapper.isNull(Device::getShopId)
               .or()
               .eq(Device::getShopId, 0);
        wrapper.orderByDesc(Device::getCreateTime);
        return this.list(wrapper);
    }

    /**
     * 填充设备标签字段
     */
    private void fillDeviceLabels(Device device) {
        // 设备状态标签
        if (device.getStatus() != null) {
            var statusLabel = EntityLabelUtils.getStatusLabel("device", device.getStatus());
            device.setStatusLabel(statusLabel.getLabel());
            device.setStatusColor(statusLabel.getColor());
            device.setIsOnline(DeviceConstants.isOnline(device.getStatus()));
        }
        
        // 填充门店信息
        if (device.getShopId() != null) {
            Shop shop = shopMapper.selectById(device.getShopId());
            if (shop != null) {
                if (device.getShopName() == null) {
                    device.setShopName(shop.getName());
                }
                // 设备自身没有地址时，回退到关联门店的地址
                if (device.getAddress() == null && shop.getAddress() != null) {
                    device.setAddress(shop.getAddress());
                }
            }
        }
        
        // 设置默认值
        setDefaultValues(device);
    }
    
    /**
     * 批量填充设备标签（避免循环内逐条查门店）
     */
    private void fillDeviceLabelsBatch(List<Device> devices) {
        if (devices == null || devices.isEmpty()) return;

        // 收集所有 shopId，批量查询
        List<Long> shopIds = devices.stream()
                .map(Device::getShopId)
                .filter(id -> id != null)
                .distinct()
                .collect(Collectors.toList());
        Map<Long, Shop> shopMap = new HashMap<>();
        if (!shopIds.isEmpty()) {
            List<Shop> shops = shopMapper.selectBatchIds(shopIds);
            if (shops != null) {
                shopMap = shops.stream().collect(Collectors.toMap(Shop::getId, s -> s));
            }
        }

        for (Device device : devices) {
            fillDeviceStatusLabel(device);
            if (device.getShopId() != null) {
                Shop shop = shopMap.get(device.getShopId());
                if (shop != null) {
                    if (device.getShopName() == null) device.setShopName(shop.getName());
                    if (device.getAddress() == null && shop.getAddress() != null)
                        device.setAddress(shop.getAddress());
                }
            }
            setDefaultValues(device);
        }
    }

    private void fillDeviceStatusLabel(Device device) {
        DeviceConstants.StatusLabel statusLabel = DeviceConstants.getStatusLabel(device.getStatus());
        if (statusLabel != null) {
            device.setStatusLabel(statusLabel.getLabel());
            device.setStatusColor(statusLabel.getColor());
            device.setIsOnline(DeviceConstants.isOnline(device.getStatus()));
        }
    }

    /**
     * 设置设备默认值
     */
    private void setDefaultValues(Device device) {
        if (device.getShopName() == null) {
            device.setShopName("默认门店");
        }
        if (device.getAddress() == null) {
            device.setAddress("-");
        }
        if (device.getDeviceTypeLabel() == null) {
            device.setDeviceTypeLabel(DeviceConstants.TYPE_SMART_CABINET);
        }
        if (device.getTemperature() == null) {
            device.setTemperature("0.0");
        }
        if (device.getVolume() == null) {
            device.setVolume(8);
        }
        if (device.getDoorCount() == null) {
            device.setDoorCount(1);
        }
        if (device.getLayerCount() == null) {
            device.setLayerCount(5);
        }
        if (device.getTotalSales() == null) {
            device.setTotalSales("0.00");
        }
        if (device.getTodaySales() == null) {
            device.setTodaySales("0.00");
        }
        if (device.getOrderCount() == null) {
            device.setOrderCount(0);
        }
    }
    
    @Override
    public List<Device> getDevicesByDoorStatus(String doorStatus) {
        try {
            return deviceMapper.selectByDoorStatus(doorStatus);
        } catch (Exception e) {
            log.error("根据门状态查询设备列表失败 - 门状态: {}", doorStatus, e);
            return java.util.Collections.emptyList();
        }
    }
    
    @Override
    public boolean updateDeviceDoorStatus(String deviceId, String doorStatus) {
        try {
            int result = deviceMapper.updateDoorStatus(deviceId, doorStatus);
            if (result > 0) {
                log.info("更新设备 {} 门状态成功: {}", deviceId, doorStatus);
                return true;
            } else {
                log.warn("更新设备 {} 门状态失败: 找不到设备或状态未改变", deviceId);
                return false;
            }
        } catch (Exception e) {
            log.error("更新设备 {} 门状态异常 - 状态: {}", deviceId, doorStatus, e);
            return false;
        }
    }
    
    @Override
    public int batchUpdateDeviceDoorStatus(java.util.Map<String, String> deviceStatusMap) {
        if (deviceStatusMap == null || deviceStatusMap.isEmpty()) {
            return 0;
        }
        
        try {
            int result = deviceMapper.batchUpdateDoorStatus(deviceStatusMap);
            log.info("批量更新设备门状态成功 - 更新数量: {}", result);
            return result;
        } catch (Exception e) {
            log.error("批量更新设备门状态异常", e);
            return 0;
        }
    }

    @Override
    public String getHahaErrorMessage(Integer errorCode, String defaultMsg) {
        if (errorCode == null) {
            return defaultMsg != null ? defaultMsg : "操作失败";
        }

        switch (errorCode) {
            case -1:
                return "参数错误，请检查设备ID是否正确";
            case -2:
                return "签名错误，请联系技术支持";
            case -3:
            case -4:
                return "认证失败，请稍后重试";
            case -100:
                return "设备不存在，请确认设备ID";
            case -101:
                return "设备离线，请稍后再试";
            case -102:
                return "设备故障，请联系设备维护人员";
            case -103:
                return "设备使用中，请稍后再试";
            default:
                return defaultMsg != null ? defaultMsg : "开门失败";
        }
    }

    @Override
    public Map<String, Object> getDeviceDetailStatus(String deviceId) {
        try {
            Map<String, Object> status = hahaClient.getDeviceApi().getDeviceStatus(deviceId);
            log.info("查询设备详细状态 - deviceId: {}, status: {}", deviceId, status);
            return status;
        } catch (Exception e) {
            log.error("查询设备详细状态失败 - deviceId: {}, error: {}", deviceId, e.getMessage());
            return null;
        }
    }

    /**
     * 从 Redis 读取支付分预授权信息，持久化到 DoorRecord
     * 此时 Redis 数据必定存在（前端刚调用过 pre-create），写入 DB 后防止后续 Redis 过期丢失
     */
    private void persistPayScorePreAuth(String deviceId, Long userId, String activityId) {
        if (payScoreService == null) {
            return;
        }
        try {
            Map<String, String> preAuth = payScoreService.getPreAuthInfo(deviceId, userId);
            if (preAuth == null) {
                log.debug("Redis 中无支付分预授权信息，跳过持久化 - deviceId: {}, userId: {}", deviceId, userId);
                return;
            }
            String payScoreOrderId = preAuth.get("payScoreOrderId");
            if (payScoreOrderId != null) {
                doorRecordService.updatePayScorePreAuth(activityId, payScoreOrderId, preAuth.get("serviceId"));
                log.info("支付分预授权信息已持久化到 DoorRecord - activityId: {}, payScoreOrderId: {}",
                        activityId, payScoreOrderId);
            }
        } catch (Exception e) {
            // 持久化失败不应阻塞开门流程，Redis 仍可作为数据源
            log.warn("持久化支付分预授权信息到 DoorRecord 失败（非致命） - deviceId: {}, userId: {}",
                    deviceId, userId, e);
        }
    }
}
