package com.weather.modules.weather.dailydata.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.weather.common.constant.Constant;
import com.weather.common.page.PageData;
import com.weather.common.redis.RedisKeys;
import com.weather.common.redis.RedisUtils;
import com.weather.common.service.impl.CrudServiceImpl;
import com.weather.common.utils.ConvertUtils;
import com.weather.common.utils.TimeUtils;
import com.weather.modules.security.user.SecurityUser;
import com.weather.modules.security.user.UserDetail;
import com.weather.modules.sys.service.SysDeptService;
import com.weather.modules.weather.dailydata.dao.WeatherDailyDataDao;
import com.weather.modules.weather.dailydata.dto.DailyWeatherSummarizeDto;
import com.weather.modules.weather.dailydata.dto.WeatherDailyDataDto;
import com.weather.modules.weather.dailydata.dto.WeatherDailyDataQueryDto;
import com.weather.modules.weather.dailydata.entity.WeatherDailyDataEntity;
import com.weather.modules.weather.dailydata.service.WeatherDailyDataService;
import com.weather.modules.weather.station.dao.WeatherStationDao;
import com.weather.modules.weather.station.entity.WeatherStationEntity;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Service
@AllArgsConstructor
public class WeatherDailyDataServiceImpl extends CrudServiceImpl<WeatherDailyDataDao, WeatherDailyDataEntity, WeatherDailyDataDto> implements WeatherDailyDataService {

    private final SysDeptService sysDeptService;
    private final WeatherStationDao weatherStationDao;
    private final WeatherDailyDataDao weatherDailyDataDao;
    private final RedisUtils redisUtils;

    @Override
    public PageData<WeatherDailyDataDto> page(Map<String, Object> params) {
        QueryWrapper<WeatherDailyDataEntity> wrapper = getWrapper(params);

        List<Long> prioritizedIds = getPrioritizedStationIds();
        if (!prioritizedIds.isEmpty()) {
            wrapper.orderByAsc("CASE WHEN station_id IN (" +
                    prioritizedIds.stream().map(String::valueOf).collect(Collectors.joining(",")) +
                    ") THEN 0 ELSE 1 END");
        }

        wrapper.orderByDesc(Constant.CREATE_DATE);

        IPage<WeatherDailyDataEntity> page = baseDao.selectPage(
                getPage(params, null, true),
                wrapper
        );
        return getPageData(page, WeatherDailyDataDto.class);
    }

    private List<Long> getPrioritizedStationIds() {
        UserDetail user = SecurityUser.getUser();
        if (ObjectUtil.isEmpty(user) || user.getDeptId() == null) return Collections.emptyList();
        List<Long> deptIds = sysDeptService.getSubDeptIdList(user.getDeptId());
        if (deptIds.isEmpty()) return Collections.emptyList();
        List<WeatherStationEntity> stations = weatherStationDao.selectList(
                new QueryWrapper<WeatherStationEntity>()
                        .select("station_code")
                        .in("dept_id", deptIds));
        return stations.stream()
                .map(s -> {
                    try {
                        return Long.valueOf(s.getStationCode());
                    } catch (NumberFormatException e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    @Override
    public QueryWrapper<WeatherDailyDataEntity> getWrapper(Map<String, Object> params) {
        String stationCode = (String) params.get("stationId");
        QueryWrapper<WeatherDailyDataEntity> wrapper = new QueryWrapper<>();
        if (StrUtil.isNotEmpty(stationCode)) {
            List<String> split = StrUtil.split(stationCode, ",");
            wrapper.in(CollectionUtil.isNotEmpty(split), "station_id", split);
        }
        return wrapper;
    }

    @Override
    public void save(WeatherDailyDataDto dto) {
        WeatherDailyDataEntity entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
        fillStationDeptId(entity);
        insert(entity);
        BeanUtils.copyProperties(entity, dto);
    }

    @Override
    public void update(WeatherDailyDataDto dto) {
        WeatherDailyDataEntity entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
        fillStationDeptId(entity);
        updateById(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean insertBatch(Collection<WeatherDailyDataEntity> entityList) {
        List<Long> stationIds = entityList.stream()
                .map(WeatherDailyDataEntity::getStationId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();
        Map<Long, Long> stationDeptMap = new HashMap<>();
        if (!stationIds.isEmpty()) {
            List<String> stationCodeList = stationIds.stream().map(String::valueOf).collect(Collectors.toList());
            List<WeatherStationEntity> stations = weatherStationDao.selectList(
                    new QueryWrapper<WeatherStationEntity>().in("station_code", stationCodeList));
            stationDeptMap = stations.stream()
                    .filter(s -> s.getDeptId() != null)
                    .collect(Collectors.toMap(s -> Long.valueOf(s.getStationCode()), WeatherStationEntity::getDeptId));
        }

        UserDetail user = SecurityUser.getUser();
        Date now = new Date();
        List<WeatherDailyDataEntity> list = (List<WeatherDailyDataEntity>) entityList;
        for (WeatherDailyDataEntity entity : list) {
            entity.setId(IdUtil.getSnowflakeNextId());
            Long deptId = stationDeptMap.get(entity.getStationId());
            entity.setDeptId(deptId != null ? deptId : user.getDeptId());
            entity.setCreator(user.getId());
            entity.setCreateDate(now);
            entity.setUpdater(user.getId());
            entity.setUpdateDate(now);
        }

        int successCount = weatherDailyDataDao.insertBatchMultiRow(list);
        log.info("成功插入{}条数据",successCount);
        return true;
    }

    private void fillStationDeptId(WeatherDailyDataEntity entity) {
        if (entity.getStationId() != null) {
            WeatherStationEntity station = weatherStationDao.selectOne(
                    new QueryWrapper<WeatherStationEntity>().eq("station_code", String.valueOf(entity.getStationId())));
            if (station != null && station.getDeptId() != null) {
                entity.setDeptId(station.getDeptId());
            }
        }
    }

    @Override
    public List<DailyWeatherSummarizeDto> dailyWeatherSummarize(
            WeatherDailyDataQueryDto queryDto) {

        List<Long> stationIds = queryDto.getStationIds();
        if (CollectionUtil.isEmpty(stationIds)) {
            return List.of();
        }

        LocalDate startTime;
        LocalDate endTime;

        if (queryDto.getSpanTime() != null) {
            LocalDate endDate = TimeUtils.convertToLocalDateTime(queryDto.getEndtDate());
            startTime = endDate.minusYears(queryDto.getSpanTime());
            endTime = endDate;
        } else {
            startTime = TimeUtils.convertToLocalDateTime(queryDto.getStartDate());
            endTime = TimeUtils.convertToLocalDateTime(queryDto.getEndtDate());
        }

        // 尝试从缓存获取（仅"历年同月同日"查询可命中缓存）
        List<DailyWeatherSummarizeDto> cached = getCachedSummarize(stationIds, startTime, endTime);
        if (cached != null) {
            return cached;
        }

        // 缓存未命中，查询数据库
        return querySummarizeFromDb(stationIds, startTime, endTime);
    }

    /**
     * 尝试从 Redis 缓存获取汇总数据
     *
     * @return 缓存命中时返回数据，未命中返回 null
     */
    @SuppressWarnings("unchecked")
    private List<DailyWeatherSummarizeDto> getCachedSummarize(
            List<Long> stationIds, LocalDate start, LocalDate end) {

        // 仅当月-日相同的跨年查询可命中缓存
        if (start.getMonth() != end.getMonth() || start.getDayOfMonth() != end.getDayOfMonth()) {
            return null;
        }

        String key = RedisKeys.getWeatherSummarizeKey(end.getMonthValue(), end.getDayOfMonth());
        Object cached = redisUtils.get(key);
        if (cached == null) {
            return null;
        }

        Map<String, List<DailyWeatherSummarizeDto>> grouped;
        try {
            grouped = (Map<String, List<DailyWeatherSummarizeDto>>) cached;
        } catch (ClassCastException e) {
            return null;
        }

        // 验证所有请求站点都在缓存中，任一缺失则降级到 DB
        for (Long sid : stationIds) {
            if (!grouped.containsKey(String.valueOf(sid))) {
                return null;
            }
        }

        // 从缓存中按站点过滤 + 限定年份范围（Key 为 String 避免 JSON 反序列化时 Long/Integer 类型丢失）
        return stationIds.stream()
                .flatMap(sid -> {
                    List<DailyWeatherSummarizeDto> stationData = grouped.get(String.valueOf(sid));
                    return stationData != null ? stationData.stream() : Stream.empty();
                })
                .filter(d -> {
                    LocalDate obsDate = d.getObserveDate().toInstant()
                            .atZone(ZoneId.systemDefault())
                            .toLocalDate();
                    return !obsDate.isBefore(start) && !obsDate.isAfter(end);
                })
                .collect(Collectors.toList());
    }

    /**
     * 直接查询数据库获取汇总数据（缓存未命中降级路径）。
     * <p>生成精确日期列表使用 IN 等值查询，命中 uk_station_date 唯一索引，
     * 避免 MONTH()/DAY()/YEAR() 函数导致全表扫描。</p>
     */
    private List<DailyWeatherSummarizeDto> querySummarizeFromDb(
            List<Long> stationIds, LocalDate startTime, LocalDate endTime) {

        int month = startTime.getMonthValue();
        int day = startTime.getDayOfMonth();
        List<String> dates = new ArrayList<>();
        for (int y = startTime.getYear(); y <= endTime.getYear(); y++) {
            dates.add(String.format("%04d-%02d-%02d", y, month, day));
        }

        List<WeatherDailyDataEntity> list = weatherDailyDataDao.selectSummarizeByMonthDay(
                stationIds, dates);

        return BeanUtil.copyToList(list, DailyWeatherSummarizeDto.class);
    }
}