package org.dromara.app.controller;


import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.app.domain.bo.AppDeviceBo;
import org.dromara.app.domain.bo.AppScheduleBo;
import org.dromara.app.domain.bo.AppSchedulingDeviceBo;
import org.dromara.app.domain.bo.AppWateringLogBo;
import org.dromara.app.domain.vo.*;
import org.dromara.app.service.*;
import org.dromara.app.service.impl.AppScheduleServiceImpl;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysOssUploadVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.domain.vo.UserInfoVo;
import org.dromara.system.service.ISysOssService;
import org.dromara.system.service.ISysUserService;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.text.SimpleDateFormat;
import java.util.*;


@Slf4j
@RequiredArgsConstructor
@RestController
//@SaCheckLogin
@RequestMapping("/app/v1")
public class AppController extends BaseController {

    private final IAppDeviceService appDeviceService;
    private final IAppScheduleService appScheduleService;
    private final IAppScheduleDetailService appScheduleDetailService;
    private final AppScheduleServiceImpl appScheduleServiceimpl;
    private final IAppSchedulingDeviceService appSchedulingDeviceService;
    private final ISysUserService userService;
    private final IAppWateringLogService appWateringLogService;
    private final IDeviceCommandService deviceCommandService;
    private final ISysOssService ossService;
    private final IAppVersionService appVersionService;

    /**
     *查询设备
     *
     */
    @GetMapping("/deviceList")
    public TableDataInfo<AppDeviceVo> list(AppDeviceBo bo, PageQuery pageQuery) {
        bo.setUserId(LoginHelper.getUserId());
        pageQuery.setOrderByColumn("createTime");
        pageQuery.setIsAsc("desc");
        return appDeviceService.queryPageList(bo, pageQuery);
    }



    /**
     * 添加设备
     *
     */
    @ApiEncrypt
    @PostMapping("/bindDeviceStatus")
    public R<Map> bindDeviceStatus(@RequestBody String body) {
        try {
            AppDeviceBo appDevice = JsonUtils.parseObject(body, AppDeviceBo.class);
            appDevice.setUserId(LoginHelper.getUserId());
            Map<String, Object> map = appDeviceService.bindDeviceStatus(appDevice);
            return R.ok(map);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 绑定已通过MQTT注册上线的设备
     */
    @ApiEncrypt
    @PostMapping("/addDevice")
    public R<AppDeviceVo> bindDevice(@RequestBody String body) {
        try {
            AppDeviceBo appDevice = JsonUtils.parseObject(body, AppDeviceBo.class);
            appDevice.setUserId(LoginHelper.getUserId());
            return R.ok(appDeviceService.bindRegisteredDevice(appDevice));
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 获取设备信息
     详细信息
     *
     * @param deviceNo 主键
     */
    @GetMapping("/device/{deviceNo}")
    public R<AppDeviceVo> getDeviceInfo(@NotNull(message = "主键不能为空")
                                  @PathVariable String deviceNo) {
        try {
            return R.ok(getOwnedDevice(deviceNo));
        } catch (Exception e) {
            return fail(e);
        }
    }


    /**
     * 修改设备信息
     *
     */
  //  @SaCheckRole("appadmin")
    @RepeatSubmit()
    @PutMapping("/updateDeviceInfo")
    public R<Void> updateDeviceInfo(@Validated @RequestBody AppDeviceBo bo) {
        try {
            assertDeviceOwned(bo.getDeviceNo());
            bo.setUserId(LoginHelper.getUserId());
            return toAjax(appDeviceService.updateByBo(bo));
        } catch (Exception e) {
            return fail(e);
        }

    }

    /**
     * 删除设备
     */
    @Log(title = "设备信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/deleteDevice/{deviceNos}")
    public R<Void> removeDevice(@NotEmpty(message = "主键不能为空")
                          @PathVariable String[] deviceNos) {
        try {
            for (String deviceNo : deviceNos) {
                assertDeviceOwned(deviceNo);
            }
            return toAjax(appDeviceService.unbindDevices(List.of(deviceNos), LoginHelper.getUserId()));
        } catch (Exception e) {
            return fail(e);
        }
    }


    /**
     * 手动开启关闭设备
     *
     */
    //  @SaCheckRole("appadmin")
    @RepeatSubmit()
    @PutMapping("/switchDevice")
    public R<Void> switchDevice(@RequestBody String body) {
        try {
            Map<String,String> map = JsonUtils.parseObject(body, Map.class);
            String deviceNo = map.get("deviceNo");
            String status = map.get("workStatus");
            String startTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            String durationMinValue = map.get("durationMin");
            Integer durationMin = StringUtils.isBlank(durationMinValue) ? null : Integer.valueOf(durationMinValue);
            //更新设备工作状态
            Boolean flag = appDeviceService.switchDevice(deviceNo, status, startTime, durationMin);
                // 记录信息到历史表
            return toAjax(flag);
        } catch (Exception e) {
            return fail(e);
        }

    }

    /**
     * 排程关联设备查询
     * @param
     * @return
     */
    @GetMapping("/scheduleDeviceList/{scheduleId}")
    public R<Map<String,Object>> scheduleDeviceList(   @PathVariable Long scheduleId) {
        try {

            Map retMap = new HashMap();
            //所有设备列表
            AppDeviceBo appDeviceBo = new AppDeviceBo();
            appDeviceBo.setUserId(LoginHelper.getUserId());
            List<AppDeviceVo> appDeviceVos = appDeviceService.queryList(appDeviceBo);

            List<AppDeviceVo> appDeviceVoList = new ArrayList();
            AppSchedulingDeviceBo schedulingDeviceBo = new AppSchedulingDeviceBo();
            schedulingDeviceBo.setScheduleId(scheduleId);


            for (AppDeviceVo appDeviceVo:appDeviceVos){
                List<AppSchedulingDeviceVo> appSchedulingDeviceVos = appSchedulingDeviceService.findByDeviceNo(appDeviceVo.getDeviceNo());
                if(appSchedulingDeviceVos.size() == 0){
                    appDeviceVoList.add(appDeviceVo);
                }
            }
            retMap.put("deviceList", appDeviceVoList);


            return R.ok(retMap);
        } catch (Exception e) {
            return fail(e);
        }
    }
    /**
     *绑定排程跟设备
     */
    @RepeatSubmit()
    @PostMapping("/addScheduleDevice")
    public R<Void> addScheduleDevice( @RequestBody String body) {
        try {
            Map<String, Object> map = JsonUtils.parseObject(body, Map.class);
            if (map == null || map.get("scheduleId") == null) {
                throw new ServiceException("app.schedule.id.not.blank.request");
            }
            Long scheduleId = Long.valueOf(map.get("scheduleId").toString());
            assertScheduleOwned(scheduleId);
            List<String> deviceNos = parseDeviceNos(map);
            if (deviceNos.isEmpty()) {
                throw new ServiceException("app.schedule.devices.not.blank");
            }

            for (String deviceNo:deviceNos){
                if (StringUtils.isBlank(deviceNo)) {
                    continue;
                }
                assertDeviceOwned(deviceNo);
                if (appSchedulingDeviceService.queryByScheduleIdAndDeviceNo(scheduleId, deviceNo) != null) {
                    continue;
                }
                AppSchedulingDeviceBo schedulingDeviceBo = new AppSchedulingDeviceBo();
                schedulingDeviceBo.setDeviceNo(deviceNo);
                schedulingDeviceBo.setScheduleId(scheduleId);
                if (appSchedulingDeviceService.insertByBo(schedulingDeviceBo)) {
                    deviceCommandService.sendScheduleBindCommand(deviceNo, buildScheduleBindPayload(scheduleId, deviceNo));
                }
            }
            return R.ok();
        } catch (Exception e) {
            return fail(e);
        }
    }

    private List<String> parseDeviceNos(Map<String, Object> map) {
        Object value = Optional.ofNullable(map.get("deviceNos"))
            .orElseGet(() -> Optional.ofNullable(map.get("deviceNo")).orElse(map.get("deviceIds")));
        if (value == null) {
            return Collections.emptyList();
        }
        if (value instanceof Collection<?> collection) {
            return collection.stream()
                .filter(Objects::nonNull)
                .map(Object::toString)
                .filter(StringUtils::isNotBlank)
                .toList();
        }
        String text = value.toString();
        if (StringUtils.isBlank(text)) {
            return Collections.emptyList();
        }
        return Arrays.stream(text.split(","))
            .map(String::trim)
            .filter(StringUtils::isNotBlank)
            .toList();
    }

    private Map<String, Object> buildScheduleBindPayload(Long scheduleId, String deviceNo) {

        List<AppScheduleDetailVo> details = appScheduleDetailService.queryByScheduleIdByStatus(scheduleId);
        Map<String, Object> payload = new HashMap<>();
        payload.put("deviceNo", deviceNo);
        payload.put("details", toScheduleDetailPayload(details));
        return payload;
    }

    private List<Map<String, Object>> toScheduleDetailPayload(List<AppScheduleDetailVo> details) {
        List<Map<String, Object>> detailPayload = new ArrayList<>();
        if (details == null) {
            return detailPayload;
        }
        for (AppScheduleDetailVo detail : details) {
            List<Object> timeSlots = new ArrayList<>();
            JSONArray timeArray = JSONUtil.parseArray(detail.getTimeData());
            for (Object timeSlot : timeArray) {
                timeSlots.add(timeSlot);
            }

            if (detail.getStatus().equals("1")){
                Map<String, Object> item = new HashMap<>();
                //  item.put("id", detail.getId());
                item.put("weekday", detail.getWeekday());
                item.put("timeData", timeSlots);
                item.put("triggerType", detail.getTriggerType());
                item.put("status", detail.getStatus());
                detailPayload.add(item);
            }

        }
        return detailPayload;
    }

    /**
     * 删除排程与设备绑定
     * @param scheduleId,deviceNo
     * @return
     */

    @Log(title = "排程与设备关联", businessType = BusinessType.DELETE)
    @DeleteMapping("/deleteScheduleDevice")
    public R<Void> deleteScheduleDevice(@RequestParam(required = false) String scheduleId,
                                        @RequestParam(required = false) String deviceNo) {
        try {
            if (StringUtils.isBlank(scheduleId)) {
                throw new ServiceException("app.schedule.id.not.blank.request");
            }
            if (StringUtils.isBlank(deviceNo)) {
                throw new ServiceException("app.device.no.not.blank");
            }
            Long scheduleIdValue = Long.valueOf(scheduleId);
            assertScheduleOwned(scheduleIdValue);
            assertDeviceOwned(deviceNo);
            Boolean deleted = appSchedulingDeviceService.deleteWithValidByScheduleIdAndDeviceNo(scheduleIdValue, deviceNo);
            if (deleted) {
                // 通知设备解绑排程
                try {
                    deviceCommandService.sendScheduleUnbindCommand(deviceNo, scheduleIdValue);
                } catch (Exception mqttEx) {
                    log.warn("[排程通知] 删除排程设备绑定后下发解绑命令失败 排程ID={} 设备编号={} 原因={}",
                        scheduleIdValue, deviceNo, mqttEx.getMessage());
                }
            }
            return toAjax(deleted);
        } catch (Exception e) {
            return fail(e);
        }
    }



    /**
     * 查询排程列表
     */
    @GetMapping("/schedulelist")
    public TableDataInfo<AppScheduleVo> schedulelist(AppScheduleBo bo, PageQuery pageQuery) {
        bo.setUserId(LoginHelper.getUserId());
        pageQuery.setOrderByColumn("createTime");
        pageQuery.setIsAsc("desc");
        return appScheduleService.queryPageList(bo, pageQuery);
    }

    /**
     * 新增排程
     *
     */
    @Log(title = "日程排列", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping("/addschedule")
    public R<AppScheduleVo> addSchedule(@Validated(AddGroup.class) @RequestBody AppScheduleBo bo) {
        try {
            bo.setCreateTime(new Date());
            AppScheduleVo appScheduleVo = appScheduleService.insertByBo(bo);
            if (ObjectUtil.isNotNull(appScheduleVo)){
                return R.ok(appScheduleVo);
            }
            return R.fail();
           // return toAjax(appScheduleService.insertByBo(bo));
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 排程詳情
     * @param
     * @param
     * @return
     */
    @GetMapping("/schedule/{id}")
    public R<AppScheduleVo> getScheduleInfo(@NotNull(message = "主键不能为空")
                                    @PathVariable Long id) {
        try {
            AppScheduleVo appScheduleVo = getOwnedSchedule(id);
            if (ObjectUtil.isNotNull(appScheduleVo)){
                List<AppScheduleDetailVo> list = appScheduleDetailService.queryByScheduleIdByStatus(appScheduleVo.getId());

                AppSchedulingDeviceBo appSchedulingDeviceBo = new AppSchedulingDeviceBo();
                appSchedulingDeviceBo.setScheduleId(appScheduleVo.getId());
                List<AppSchedulingDeviceVo> appSchedulingDeviceVos = appSchedulingDeviceService.queryList(appSchedulingDeviceBo);
                List deviceList = new ArrayList();
                for (AppSchedulingDeviceVo appSchedulingDeviceVo:appSchedulingDeviceVos){
                    AppDeviceVo appDeviceVo = appDeviceService.queryById(appSchedulingDeviceVo.getDeviceNo());
                    if (ObjectUtil.isNotNull(appDeviceVo)){
                        deviceList.add(appDeviceVo);
                    }

                }
                List detailList = new ArrayList();

                for (AppScheduleDetailVo detail:list){
                    List timeSlots = new ArrayList();
                    JSONArray mapArray = JSONUtil.parseArray(detail.getTimeData());
                    for (Object timeSlot:mapArray){
                        timeSlots.add(timeSlot);
                    }

                    Map map = new HashMap();
                    map.put("id",detail.getId() );
                    map.put("weekday",detail.getWeekday() );
                    map.put("timeData",timeSlots );
                    map.put("triggerType",detail.getTriggerType() );
                    map.put("status",detail.getStatus() );
                    detailList.add(map);
                }



                appScheduleVo.setDetails(detailList);
                appScheduleVo.setDeviceNos(deviceList);
                return R.ok(appScheduleVo);
            }


            return R.fail(appScheduleVo);
        } catch (Exception e) {
            return fail(e);
        }
    }


    /**
     * 修改排程状态
     * @param
     * @return
     */
    @PutMapping("/editScheduleStatus")
    public R<Void> editScheduleStatus(@NotNull(message = "主键不能为空")
                                            @RequestBody AppScheduleBo bo) {
        try {
            assertScheduleOwned(bo.getId());
            bo.setUserId(LoginHelper.getUserId());
            Boolean updated = appScheduleService.updateStatusByBo(bo);
            if (updated) {
                // 向所有绑定设备下发排程状态变更
                notifyBoundDevicesScheduleStatusChange(bo.getId(), bo.getStatus());
            }
            return toAjax(updated);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 修改日程排列
     */
    @Log(title = "日程排列", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping("/updataschedule")
    public R<Object> editSchedule(@Validated(EditGroup.class) @RequestBody AppScheduleBo bo) {
        try {
            assertScheduleOwned(bo.getId());
            bo.setUserId(LoginHelper.getUserId());
            Map<String, Object> map = appScheduleService.updateByBo(bo);
            if (Boolean.valueOf(map.get("flag").toString())){
                // 向所有绑定设备下发排程更新
                notifyBoundDevicesScheduleUpdate(bo.getId());
                return R.ok(map.get("appSchedule"));
            }else {
                return R.fail();
            }
        } catch (Exception e) {
            return fail(e);
        }

    }


    /**
     * 删除日程排列
     *
     * @param ids 主键串
     */
    @Log(title = "日程排列", businessType = BusinessType.DELETE)
    @DeleteMapping("/deleteschedule/{ids}")
    public R<Void> removeSchedule(@NotEmpty(message = "主键不能为空")
                          @PathVariable Long[] ids) {
        try {
            for (Long id : ids) {
                assertScheduleOwned(id);
            }
            Map<Long, List<String>> boundDeviceNosBySchedule = new HashMap<>();
            for (Long id : ids) {
                boundDeviceNosBySchedule.put(id, findBoundDeviceNos(id));
            }
            Boolean deleted = appScheduleService.deleteWithValidByIds(List.of(ids), true);
            if (deleted) {
                for (Map.Entry<Long, List<String>> entry : boundDeviceNosBySchedule.entrySet()) {
                    notifyScheduleUnbind(entry.getKey(), entry.getValue());
                }
            }
            return toAjax(deleted);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 历史
     * 查询浇水记录列表
     */
    @GetMapping("/waterLogList")
    public TableDataInfo<AppWateringLogVo> list(AppWateringLogBo bo, PageQuery pageQuery) {
        bo.setUserId(LoginHelper.getUserId());
        pageQuery.setOrderByColumn("createTime");
        pageQuery.setIsAsc("desc");
        TableDataInfo<AppWateringLogVo> appWateringLogVoTableDataInfo = appWateringLogService.queryPageList(bo, pageQuery);
        AppDeviceBo appDeviceBo = new AppDeviceBo();
        appDeviceBo.setUserId(LoginHelper.getUserId());
        List<AppDeviceVo> appDeviceVos = appDeviceService.queryList(appDeviceBo);
        List l = new ArrayList<>();
        for (AppWateringLogVo vo : appWateringLogVoTableDataInfo.getRows()){
            for(AppDeviceVo appDeviceVo:appDeviceVos){
                if (appDeviceVo.getDeviceNo().equals(vo.getDeviceNo())){
                    vo.setDeviceName(appDeviceVo.getDeviceName());
                    vo.setDeviceNo(appDeviceVo.getDeviceNo());
                }
            }
          //  vo.setTriggerType(vo.getTriggerType().equals("0")?"排程":"手动");
            l.add(vo);
        }
        appWateringLogVoTableDataInfo.setRows(l);
        return appWateringLogVoTableDataInfo;
    }

    /**
     * 查询设备最新进行中的浇水记录
     */
    @GetMapping("/latestWaterLog")
    public R<Map<String, Object>> latestWaterLog(@RequestParam String deviceNo) {
        try {
            assertDeviceOwned(deviceNo);

            AppWateringLogBo bo = new AppWateringLogBo();
            bo.setUserId(LoginHelper.getUserId());
            bo.setDeviceNo(deviceNo);
            bo.setStatus("1");
            List<AppWateringLogVo> logs = appWateringLogService.queryList(bo);
            if (logs == null || logs.isEmpty()) {
                return R.ok(null);
            }

            AppWateringLogVo latest = logs.stream()
                .max(Comparator.comparing(AppWateringLogVo::getId, Comparator.nullsFirst(Long::compareTo)))
                .orElse(null);
            if (latest == null) {
                return R.ok(null);
            }

            Map<String, Object> result = new HashMap<>();
            result.put("startTime", formatSecondTime(latest.getStartTime()));
            result.put("endTime", formatSecondTime(latest.getEndTime()));
            return R.ok(result);
        } catch (Exception e) {
            return fail(e);
        }
    }


    /**
     * 删除浇水记录
     *
     * @param ids 主键串
     */
    @Log(title = "浇水记录", businessType = BusinessType.DELETE)
    @DeleteMapping("/waterLog/{ids}")
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
                          @PathVariable Long[] ids) {
        try {
            for (Long id : ids) {
                assertWateringLogOwned(id);
            }
            return toAjax(appWateringLogService.deleteWithValidByIds(List.of(ids), true));
        } catch (Exception e) {
            return fail(e);
        }
    }


    /**
     * 数据统计
     */
    @GetMapping("/dataStatistics")
    public R<Map> dataStatistics() {
        Map map  = new HashMap();
        try {
            Long userId = LoginHelper.getUserId();
            //总浇水次数
            Long count = appWateringLogService.queryCount(userId);
            //本月浇水次数
            Long monCount = appWateringLogService.queryByMonCount(userId);
            //本周浇水次数
            Long weekCount = appWateringLogService.queryByWeekCount(userId);
            //排程浇水次数
            Long schedule = appWateringLogService.queryByTriggerTypeCount(userId, "0");
            //手动浇水次数
            Long manualNum = appWateringLogService.queryByTriggerTypeCount(userId, "1");
            //总浇水时间
            Long allTime = Optional.ofNullable(appWateringLogService.queryByTotalWateringTime(userId)).orElse(0L);
            //平均浇水时间
            Long averageTime = count == null || count == 0 ? 0L : allTime / count;

            map.put("count",count);
            map.put("monCount",monCount);
            map.put("weekCount",weekCount);
            map.put("schedule",schedule);
            map.put("manualNum",manualNum);
            map.put("allTime",allTime);
            map.put("averageTime",averageTime);
            return R.ok(map);
        } catch (Exception e) {
            return fail(e);
        }

    }

    /**
     * 获取用户信息
     *
     * @return 用户信息
     */
    @GetMapping("/getUserInfo")
    public R<UserInfoVo> getUserInfo() {
        try {
            UserInfoVo userInfoVo = new UserInfoVo();
            LoginUser loginUser = LoginHelper.getLoginUser();
            SysUserVo user = DataPermissionHelper.ignore(() -> userService.selectUserById(loginUser.getUserId()));
            if (ObjectUtil.isNull(user)) {
                return R.fail(MessageUtils.message("app.user.data.denied"));
            }
            userInfoVo.setUser(user);
            userInfoVo.setPermissions(loginUser.getMenuPermission());
            userInfoVo.setRoles(loginUser.getRolePermission());
            return R.ok(userInfoVo);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 上传图片到OSS对象存储
     */
    @PostMapping(value = "/uploadImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public R<SysOssUploadVo> uploadImage(@RequestPart("file") MultipartFile file) {
        try {
            if (file == null || file.isEmpty()) {
                throw new ServiceException("上传图片不能为空");
            }
            String contentType = file.getContentType();
            if (StringUtils.isBlank(contentType) || !contentType.toLowerCase(Locale.ROOT).startsWith("image/")) {
                throw new ServiceException("只能上传图片文件");
            }

            SysOssVo oss = ossService.upload(file);
            SysOssUploadVo uploadVo = new SysOssUploadVo();
            uploadVo.setUrl(oss.getUrl());
            uploadVo.setFileName(oss.getOriginalName());
            uploadVo.setOssId(oss.getOssId() == null ? null : oss.getOssId().toString());
            return R.ok(uploadVo);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 检查APP版本更新
     */
    @GetMapping("/checkVersion")
    public R<AppVersionCheckVo> checkVersion(@RequestParam(required = false, defaultValue = "android") String platform,
                                             @RequestParam(required = false) String currentVersion) {
        try {
            if (StringUtils.isBlank(currentVersion)) {
                throw new ServiceException("当前版本不能为空");
            }
            String normalizedPlatform = StringUtils.lowerCase(StringUtils.trim(platform));
            if (!StringUtils.equalsAny(normalizedPlatform, "android", "ios")) {
                throw new ServiceException("平台类型仅支持 android 或 ios");
            }

            AppVersionVo version = appVersionService.queryLatestByPlatform(normalizedPlatform);
            if (version == null || StringUtils.isBlank(version.getLatestVersion())) {
                throw new ServiceException("版本配置不存在");
            }

            AppVersionCheckVo result = new AppVersionCheckVo();
            result.setPlatform(normalizedPlatform);
            result.setCurrentVersion(currentVersion);
            result.setLatestVersion(version.getLatestVersion());
            result.setVersionCode(version.getVersionCode());
            result.setUpdateAvailable(!StringUtils.equals(currentVersion, version.getLatestVersion()));
            result.setForceUpdate(StringUtils.equals(version.getForceUpdate(), "1"));
            result.setDownloadUrl(version.getDownloadUrl());
            result.setReleaseNotes(version.getReleaseNotes());
            return R.ok(result);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 修改用户
     */
   // @SaCheckPermission("system:user:edit")
    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping("/updataUser")
    public R<Void> editUser( @RequestBody SysUserBo user) {
        try {
            userService.checkUserAllowed(user.getUserId());
            userService.checkUserDataScope(user.getUserId());
            if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
                return R.fail(MessageUtils.message("app.user.phone.exists", user.getUserName()));
            }

            return toAjax(userService.updateAppUser(user));
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * 修改登录密码
     */
    @RepeatSubmit()
    @PutMapping("/updataPassword")
    public R<Void> updataPassword(@RequestBody  SysUserVo vo) {
        try {
            Long userId = LoginHelper.getUserId();
            SysUserVo sysUserVo = userService.selectUserById(userId);
            if (!BCrypt.checkpw(vo.getOldPassword(), sysUserVo.getPassword())){
                throw new UserException("user.password.not.match");
            }
            SysUserBo userBo = new SysUserBo();
            userBo.setUserId(userId);
            userBo.setPassword(vo.getPassword());
            return toAjax(userService.updateUserPas(userBo));
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * app用户手机号/邮箱找回忘记密码
     */
    @ApiEncrypt
    @PostMapping("/retrievePassword")
    public R<Void> forgot(@RequestBody String body) {
        try {
            SysUserBo bo = JsonUtils.parseObject(body, SysUserBo.class);
            bo.setUserId(LoginHelper.getUserId());

            if(Validator.isEmail(bo.getUserName())){
                boolean checkPhoneFlag = userService.checkEmailUnique(bo);
                if (!checkPhoneFlag){
                    throw new UserException("user.email.not.username");
                }
            }else if (Validator.isMobile(bo.getUserName())){
                boolean checkPhoneFlag = userService.checkPhoneUnique(bo);
                if (!checkPhoneFlag){
                    throw new UserException("user.mobile.phone.number.not.username");
                }
            }

            return toAjax(userService.updateUserPas(bo));
        } catch (Exception e) {
            return fail(e);
        }
    }



    @GetMapping("/test")
    public  R<Void>  test (){
        try {
            String bindDevice = deviceCommandService.sendBindDeviceCommand("01");

            // String s = deviceCommandService.sendSwitchCommand("01", "1", "2026-06-10 14:35:31", 20);
            return R.ok(bindDevice);
        } catch (Exception e) {
            return fail(e);
        }
    }

    private <T> R<T> fail(Exception e) {
        return R.fail(toI18nErrorMessage(e));
    }

    private String toI18nErrorMessage(Exception e) {
        String message = e.getMessage();
        if (StringUtils.isBlank(message)) {
            return MessageUtils.message("operation.fail");
        }
        String i18nMessage = MessageUtils.message(message);
        return message.equals(i18nMessage) ? message : i18nMessage;
    }

    private String formatSecondTime(Date time) {
        return time == null ? null : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
    }

    private AppDeviceVo getOwnedDevice(String deviceNo) {
        if (StringUtils.isBlank(deviceNo)) {
            throw new ServiceException("app.device.no.not.blank");
        }
        AppDeviceVo device = appDeviceService.queryById(deviceNo);
        Long userId = LoginHelper.getUserId();
        if (device == null || !Objects.equals(device.getUserId(), userId)) {
            throw new ServiceException("app.device.not.exists.or.denied");
        }
        return device;
    }

    private void assertDeviceOwned(String deviceNo) {
        getOwnedDevice(deviceNo);
    }

    private AppScheduleVo getOwnedSchedule(Long scheduleId) {
        if (scheduleId == null) {
            throw new ServiceException("app.schedule.id.not.blank");
        }
        AppScheduleVo schedule = appScheduleService.queryById(scheduleId);
        Long userId = LoginHelper.getUserId();
        if (schedule == null || !Objects.equals(schedule.getUserId(), userId)) {
            throw new ServiceException("app.schedule.not.exists.or.denied");
        }
        return schedule;
    }

    private void assertScheduleOwned(Long scheduleId) {
        getOwnedSchedule(scheduleId);
    }

    private void assertWateringLogOwned(Long id) {
        if (id == null) {
            throw new ServiceException("app.watering.log.id.not.blank");
        }
        AppWateringLogVo log = appWateringLogService.queryById(id);
        Long userId = LoginHelper.getUserId();
        if (log == null || !Objects.equals(log.getUserId(), userId)) {
            throw new ServiceException("app.watering.log.not.exists.or.denied");
        }
    }

    /**
     * 查找排程关联的所有设备编号
     */
    private List<String> findBoundDeviceNos(Long scheduleId) {
        AppSchedulingDeviceBo bo = new AppSchedulingDeviceBo();
        bo.setScheduleId(scheduleId);
        List<AppSchedulingDeviceVo> bindings = appSchedulingDeviceService.queryList(bo);
        return bindings.stream()
            .map(AppSchedulingDeviceVo::getDeviceNo)
            .filter(StringUtils::isNotBlank)
            .toList();
    }

    /**
     * 向排程关联的所有设备重新下发排程信息（用于排程内容/状态变更）
     */
    private void notifyBoundDevicesScheduleUpdate(Long scheduleId) {
        List<String> deviceNos = findBoundDeviceNos(scheduleId);
        for (String deviceNo : deviceNos) {
            try {
                deviceCommandService.sendScheduleBindCommand(deviceNo, buildScheduleBindPayload(scheduleId, deviceNo));
            } catch (Exception e) {
                log.warn("[排程通知] 下发排程更新失败 排程ID={} 设备编号={} 原因={}", scheduleId, deviceNo, e.getMessage());
            }
        }
    }

    /**
     * 按排程开关状态下发不同主题：关闭走取消主题，开启重新下发排程。
     */
    private void notifyBoundDevicesScheduleStatusChange(Long scheduleId, String status) {
        List<String> deviceNos = findBoundDeviceNos(scheduleId);
        for (String deviceNo : deviceNos) {
            try {
                if ("0".equals(status)) {
                    deviceCommandService.sendScheduleCanceledCommand(deviceNo, scheduleId);
                } else {
                    deviceCommandService.sendScheduleBindCommand(deviceNo, buildScheduleBindPayload(scheduleId, deviceNo));
                }
            } catch (Exception e) {
                log.warn("[排程通知] 下发排程状态变更失败 排程ID={} 状态={} 设备编号={} 原因={}",
                    scheduleId, status, deviceNo, e.getMessage());
            }
        }
    }

    /**
     * 向排程关联的所有设备下发排程解绑通知
     */
    private void notifyBoundDevicesScheduleUnbind(Long scheduleId) {
        notifyScheduleUnbind(scheduleId, findBoundDeviceNos(scheduleId));
    }

    private void notifyScheduleUnbind(Long scheduleId, List<String> deviceNos) {
        for (String deviceNo : deviceNos) {
            try {
                deviceCommandService.sendScheduleUnbindCommand(deviceNo, scheduleId);
            } catch (Exception e) {
                log.warn("[排程通知] 下发排程解绑失败 排程ID={} 设备编号={} 原因={}", scheduleId, deviceNo, e.getMessage());
            }
        }
    }

}
