package com.kym.service.cache;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.kym.common.cache.PlatformCache;
import com.kym.entity.admin.ConnectorInfo;
import com.kym.entity.admin.EquipmentRelation;
import com.kym.entity.admin.Platform;
import com.kym.entity.admin.Station;
import com.kym.entity.common.RedisKeys;
import com.kym.entity.miniapp.vo.PlatformPolicyInfoVo;
import com.kym.service.admin.ConnectorInfoService;
import com.kym.service.admin.EquipmentRelationService;
import com.kym.service.admin.PlatformService;
import com.kym.service.admin.StationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author skyline
 * @description 缓存
 * @date 2023-08-25 22:58
 */
public enum KymCache {
    INSTANCE;

    private static final Logger log = LoggerFactory.getLogger(KymCache.class);

    /**
     * 缓存过期时间：24小时
     */
    private static final long CACHE_EXPIRE_HOURS = 24;

    /**
     * 带数据源切换的数据库查询（用于缓存miss时按需加载）
     *
     * @param supplier 数据库查询逻辑
     * @param <T>      返回类型
     * @return 查询结果
     */
    private <T> T executeWithDataSource(Supplier<T> supplier) {
        DynamicDataSourceContextHolder.push("db-admin");
        try {
            return supplier.get();
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }

    /**
     * 获取枪头状态（带空值保护和缓存miss时按需加载）
     *
     * @param connectorId 充电接口ID
     * @return 状态值，如果不存在返回null
     */
    public Integer getConnectorStatus(String connectorId) {
        if (connectorId == null) {
            return null;
        }
        try {
            String value = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_STATUS + connectorId);
            if (value == null) {
                // 缓存miss，从数据库加载
                Integer status = loadConnectorStatusFromDb(connectorId);
                if (status != null) {
                    // 重新缓存
                    KymCacheInjector.redisTemplate.opsForValue()
                            .set(RedisKeys.CONNECTOR_ID_TO_STATUS + connectorId, String.valueOf(status), CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
                }
                return status;
            }
            if ("-1".equals(value)) {
                return null;
            }
            return Integer.valueOf(value);
        } catch (Exception e) {
            log.warn("获取枪头状态失败，connectorId: {}", connectorId, e);
            return null;
        }
    }

    /**
     * 从数据库加载枪头状态
     */
    private Integer loadConnectorStatusFromDb(String connectorId) {
        return executeWithDataSource(() -> {
            ConnectorInfoService service = SpringUtil.getBean(ConnectorInfoService.class);
            ConnectorInfo info = service.lambdaQuery().eq(ConnectorInfo::getConnectorId, connectorId).one();
            return info != null ? info.getStatus() : null;
        });
    }

    /**
     * 缓存枪头状态（带过期时间）
     *
     * @param map 状态映射
     */
    public void putConnectorId2Status(Map<String, Integer> map) {
        map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
                .set(RedisKeys.CONNECTOR_ID_TO_STATUS + k, String.valueOf(v), CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
    }


    /**
     * 缓存枪头短编号（带过期时间，移除本地Map缓存）
     *
     * @param map 短编号映射
     */
    public void putConnectorId2ShortId(Map<String, String> map) {
        map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
                .set(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
    }

    /**
     * 缓存短编号到connectorId的反向索引（带过期时间）
     *
     * @param map 短编号 -> connectorId 映射
     */
    public void putShortId2ConnectorId(Map<String, String> map) {
        map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
                .set(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
    }

    /**
     * 通过设备编号或者枪头编号获取设备短编号（带缓存miss时按需加载）
     *
     * @param id 设备编号或枪头编号
     * @return 短编号
     */
    public String getShortIdByEquipmentIdOrConnectorId(String id) {
        var connectorId = getConnectorId(id);
        if (connectorId == null) {
            return null;
        }
        String shortId = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + connectorId);
        if (shortId == null) {
            // 缓存miss，从数据库加载
            EquipmentRelation relation = loadEquipmentRelationByConnectorId(connectorId);
            if (relation != null) {
                shortId = relation.getShortId();
                // 重新缓存
                KymCacheInjector.redisTemplate.opsForValue()
                        .set(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + connectorId, shortId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
                // 同时缓存反向索引
                KymCacheInjector.redisTemplate.opsForValue()
                        .set(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + shortId, connectorId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            }
        }
        return shortId;
    }

    /**
     * 从数据库加载设备关系
     */
    private EquipmentRelation loadEquipmentRelationByConnectorId(String connectorId) {
        return executeWithDataSource(() -> {
            EquipmentRelationService service = SpringUtil.getBean(EquipmentRelationService.class);
            return service.getByConnectorId(connectorId);
        });
    }


    public void putConnectorId2StationId(Map<String, String> map) {
        map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
                .set(RedisKeys.CONNECTOR_ID_TO_STATION_ID + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
    }

    /**
     * 通过设备编号或者枪头编号获取站点id（带缓存miss时按需加载）
     *
     * @param id 设备编号或枪头编号
     * @return 站点ID
     */
    public String getStationIdByEquipmentIdOrConnectorId(String id) {
        var connectorId = getConnectorId(id);
        if (connectorId == null) {
            return null;
        }
        String stationId = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_STATION_ID + connectorId);
        if (stationId == null) {
            // 缓存miss，从数据库加载
            EquipmentRelation relation = loadEquipmentRelationByConnectorId(connectorId);
            if (relation != null) {
                stationId = relation.getStationId();
                // 重新缓存
                KymCacheInjector.redisTemplate.opsForValue()
                        .set(RedisKeys.CONNECTOR_ID_TO_STATION_ID + connectorId, stationId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            }
        }
        return stationId;
    }

    public void putStationId2Name(Map<String, String> map) {
        map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
                .set(RedisKeys.STATION_ID_TO_NAME + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
    }

    /**
     * 通过站点id获取站点名称（带缓存miss时按需加载）
     *
     * @param stationId 站点ID
     * @return 站点名称
     */
    public String getStationNameById(String stationId) {
        if (stationId == null) {
            return null;
        }
        String stationName = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.STATION_ID_TO_NAME + stationId);
        if (stationName == null) {
            // 缓存miss，从数据库加载
            stationName = loadStationNameFromDb(stationId);
            if (stationName != null) {
                // 重新缓存
                KymCacheInjector.redisTemplate.opsForValue()
                        .set(RedisKeys.STATION_ID_TO_NAME + stationId, stationName, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            }
        }
        return stationName;
    }

    /**
     * 从数据库加载站点名称
     */
    private String loadStationNameFromDb(String stationId) {
        return executeWithDataSource(() -> {
            StationService service = SpringUtil.getBean(StationService.class);
            Station station = service.lambdaQuery().eq(Station::getStationId, stationId).one();
            return station != null ? station.getStationName() : null;
        });
    }

    /**
     * 通过充电接口id获取站点名称（复用getStationNameById的缓存miss加载逻辑）
     *
     * @param connectorId 充电接口ID
     * @return 站点名称
     */
    public String getStationNameByConnectorId(String connectorId) {
        var stationId = getStationIdByEquipmentIdOrConnectorId(connectorId);
        return getStationNameById(stationId);
    }

    /**
     * 操作员对应有权限的站点
     *
     * @param map
     */
    public void putAdminUser2Stations(Map<Long, List<String>> map) {
        map.forEach((k, v) -> {
            String key = RedisKeys.ADMIN_USER_STATION_IDS + k;
            String[] values = v.toArray(new String[0]); // 优化数组创建方式，使用varargs来避免不必要的数组拷贝
            KymCacheInjector.redisTemplate.delete(key);
            KymCacheInjector.redisTemplate.opsForSet().add(key, values);
        });
    }


    /**
     * 获取运营平台操作员有权限的站点ID
     *
     * @param adminUserId
     * @return
     */
    public List<String> getAdminUserStationIds(Long adminUserId) {
        return KymCacheInjector.redisTemplate.opsForSet().members(RedisKeys.ADMIN_USER_STATION_IDS + adminUserId).stream().toList();
    }

    public void putConnectorId2ParkingNo(Map<String, String> map) {
        map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
                .set(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + k, v, CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
    }

    /**
     * 站点费率（带过期时间）
     *
     * @param map 费率映射
     */
    public void putStationId2PolicyInfo(Map<String, List<PlatformPolicyInfoVo>> map) {
        map.forEach((k, v) -> KymCacheInjector.redisTemplate.opsForValue()
                .set(RedisKeys.STATION_ID_POLICY_INFO + k, JSONObject.toJSONString(v), CACHE_EXPIRE_HOURS, TimeUnit.HOURS));
    }

    /**
     * 获取站点费率
     *
     * @param stationId
     * @return
     */
    public List<PlatformPolicyInfoVo> getStationPolicyInfo(String stationId) {
        var json = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.STATION_ID_POLICY_INFO + stationId);
        return JSON.parseArray(json, PlatformPolicyInfoVo.class);
    }

    /**
     * 通过设备编号或枪头编号获取停车位号（带缓存miss时按需加载）
     *
     * @param id 设备编号或枪头编号
     * @return 停车位号
     */
    public String getParkNoByEquipmentIdOrConnectorId(String id) {
        var connectorId = getConnectorId(id);
        if (connectorId == null) {
            return null;
        }
        String parkingNo = KymCacheInjector.redisTemplate.opsForValue().get(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + connectorId);
        if (parkingNo == null) {
            // 缓存miss，从数据库加载
            EquipmentRelation relation = loadEquipmentRelationByConnectorId(connectorId);
            if (relation != null) {
                parkingNo = relation.getParkingNo();
                // 重新缓存
                KymCacheInjector.redisTemplate.opsForValue()
                        .set(RedisKeys.CONNECTOR_ID_TO_PARKING_NO + connectorId, parkingNo, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            }
        }
        return parkingNo;
    }

    /**
     * 将equipmentId,connectorId,shortId转化成17位的connectorId
     * 从Redis查找短编号对应的完整connectorId
     *
     * @param id 设备ID（可能是17位、16位或6位短编号）
     * @return 17位的connectorId
     */
    public String getConnectorId(String id) {
        if (id == null) {
            return null;
        }
        return switch (id.length()) {
            case 17 -> id;
            case 16 -> id.concat("1");
            case 6 -> {
                // 从Redis反向查找短编号对应的connectorId
                String connectorId = getConnectorIdByShortId(id);
                yield connectorId;
            }
            default -> null;
        };
    }

    /**
     * 通过短编号从Redis查找对应的connectorId（带缓存miss时按需加载）
     *
     * @param shortId 短编号
     * @return connectorId
     */
    private String getConnectorIdByShortId(String shortId) {
        // 从Redis反向索引中查找
        String connectorId = KymCacheInjector.redisTemplate.opsForValue()
                .get(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + shortId);
        if (connectorId != null) {
            return connectorId;
        }
        // 缓存miss，从数据库加载
        connectorId = loadConnectorIdByShortId(shortId);
        if (connectorId != null) {
            // 重新缓存反向索引
            KymCacheInjector.redisTemplate.opsForValue()
                    .set(RedisKeys.SHORT_ID_TO_CONNECTOR_ID + shortId, connectorId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            // 同时缓存正向索引
            KymCacheInjector.redisTemplate.opsForValue()
                    .set(RedisKeys.CONNECTOR_ID_TO_SHORT_ID + connectorId, shortId, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            return connectorId;
        }
        log.warn("未找到短编号对应的connectorId，shortId: {}", shortId);
        return null;
    }

    /**
     * 从数据库通过短编号加载connectorId
     */
    private String loadConnectorIdByShortId(String shortId) {
        return executeWithDataSource(() -> {
            EquipmentRelationService service = SpringUtil.getBean(EquipmentRelationService.class);
            EquipmentRelation relation = service.getByShortId(shortId);
            return relation != null ? relation.getConnectorId() : null;
        });
    }


    @Component
    public static class KymCacheInjector implements ApplicationListener<ApplicationStartedEvent> {

        private static final StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);

        private final EquipmentRelationService equipmentRelationService;
        private final PlatformService platformService;
        private final StationService stationService;
        private final ConnectorInfoService connectorInfoService;


        @Value("${spring.profiles.active}")
        public String env;


        private KymCacheInjector(EquipmentRelationService equipmentRelationService, StationService stationService, ConnectorInfoService connectorInfoService, PlatformService platformService) {
            this.equipmentRelationService = equipmentRelationService;
            this.stationService = stationService;
            this.connectorInfoService = connectorInfoService;
            this.platformService = platformService;
        }

        @Override
        public void onApplicationEvent(ApplicationStartedEvent event) {
            // 手动切换数据源
            DynamicDataSourceContextHolder.push("db-admin");
            // 将数据库数据缓存到redis
            var equipmentRelations = equipmentRelationService.list();

            var connectorId2ShortId = equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getConnectorId, EquipmentRelation::getShortId));
            KymCache.INSTANCE.putConnectorId2ShortId(connectorId2ShortId);
            // 同时建立反向索引：短编号 -> connectorId
            var shortId2ConnectorId = equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getShortId, EquipmentRelation::getConnectorId));
            KymCache.INSTANCE.putShortId2ConnectorId(shortId2ConnectorId);

            var connector2Station = equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getConnectorId, EquipmentRelation::getStationId));
            KymCache.INSTANCE.putConnectorId2StationId(connector2Station);

            var stationId2Name = stationService.list().stream().collect(Collectors.toMap(Station::getStationId, Station::getStationName));
            KymCache.INSTANCE.putStationId2Name(stationId2Name);

            var connectorId2ParkingNo = equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getConnectorId, EquipmentRelation::getParkingNo));
            KymCache.INSTANCE.putConnectorId2ParkingNo(connectorId2ParkingNo);

            var connectorId2Status = connectorInfoService.list().stream().collect(Collectors.toMap(ConnectorInfo::getConnectorId, ConnectorInfo::getStatus));
            KymCache.INSTANCE.putConnectorId2Status(connectorId2Status);


            // 缓存平台数据
            PlatformCache.INSTANCE.putPlatformName2Config(platformService.lambdaQuery().eq(Platform::getEnv, env).list().stream().collect(Collectors.toMap(Platform::getPlatformName, platform -> platform)));
            PlatformCache.INSTANCE.putConnectorId2PlatformName(equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getConnectorId, EquipmentRelation::getPlatformName)));
            PlatformCache.INSTANCE.putStationId2PlatformName(equipmentRelations.stream().collect(Collectors.toMap(EquipmentRelation::getStationId, EquipmentRelation::getPlatformName, (oldValue, newValue) -> oldValue)));
            DynamicDataSourceContextHolder.poll();
        }
    }
}
