package com.bizzan.bitrade.util;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.bizzan.bitrade.constant.Platform;
import com.bizzan.bitrade.entity.Admin;
import com.bizzan.bitrade.entity.AppRevision;
import com.bizzan.bitrade.entity.Member;
import com.bizzan.bitrade.entity.SysConfig;
import com.bizzan.bitrade.entity.transform.AuthMember;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class RedisManager {
	@Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Resource(name = "redisTemplate")
    private ValueOperations<Object,Object> valOps;

    @Resource(name = "redisTemplate")
    private SetOperations<Object,Object> setOps;

    public void save(Object key, Object value, long time){
        valOps.set(key,value,time,TimeUnit.SECONDS);
    }

    public Long getExpire(Object key) {
        return redisTemplate.getExpire(key);
    }

    public Object get(Object key){
        return valOps.get(key);
    }

    public void saveSetValue(Object key , Object ...value) {
        setOps.add(key, value);
    }

    public void saveSetValue(Object key , long time , Object ...value) {
        setOps.add(key, value,time,TimeUnit.SECONDS);
    }

    public boolean hasKey(Object key) {
        return redisTemplate.hasKey(key);
    }

    public void delete(Object key) {
        redisTemplate.delete(key);
    }

    public Long getLong(Object key) {
        try {
            Object res = valOps.get(key);
            return res == null ? null : Long.parseLong(res.toString());
        }catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
    
    public static final String TOKEN_PREFIX = "TOKEN:";
    public static final String MEMBER_INFO_PREFIX = "MEMBER_INFO:";
    public static final String TRADING_ACCOUNT_PREFIX = "TRADING_ACCOUNT:";
    public static final String FOLLOW_ACCOUNT_PREFIX = "FOLLOW_ACCOUNT:";
    public static final String EXCHANGE_ACCOUNT_PREFIX = "EXCHANGE_ACCOUNT:";
    public static final String SYS_CONFIG_PREFIX = "SYS_CONFIG:";
    public static final String APP_REVISION_PREFIX = "APP_REVISION:";
    public static final String SPECAIL_TRADER_STATUS_PREFIX = "SPECIAL_TRADER_STATUS:";
    public static final String BLAST_ALARM_PREFIX = "BLAST_ALARM:";
    public static final String BLAST_ALARM_FOLLOW_PREFIX = "BLAST_ALARM_FOLLOW:";
    /** 全仓爆仓邮件去重 key 前缀，24小时内同一用户只发一次 */
    public static final String BLAST_EMAIL_PREFIX = "BLAST_EMAIL:";
    /** 逐仓爆仓邮件去重 key 前缀，24小时内同一用户只发一次 */
    public static final String BLAST_POSITION_EMAIL_PREFIX = "BLAST_POSITION_EMAIL:";
    public static final String SYMBOL_PRICE_BIAS_PREFIX = "SYMBOL_PRICE_BIAS:";
    public static final String FUNDING_RATE_PREFIX = "FUNDING_RATE:";
    public static final String BACK_LENGTH_PREFIX = "BACK_LENGTH:";
    
    public void saveMemberInfo(Long id, Object tradingAccount) {
    	String key = MEMBER_INFO_PREFIX+id;
    	valOps.set(key,tradingAccount,1,TimeUnit.DAYS);
    }
    public Object getMemberInfo(Long id) {
    	String key = MEMBER_INFO_PREFIX+id;
    	try {
    		return (Object)valOps.get(key);
    	} catch (Exception e) {
    		return null;
    	}
    }
    public void removeMemberInfo(Long id) {
    	String key = MEMBER_INFO_PREFIX+id;
    	redisTemplate.delete(key);
    }
    
    public void saveTradingAccount(Long id, Object tradingAccount) {
    	String key = TRADING_ACCOUNT_PREFIX+id;
    	valOps.set(key,tradingAccount,1,TimeUnit.DAYS);
    }
    public Object getTradingAccount(Long id) {
    	String key = TRADING_ACCOUNT_PREFIX+id;
    	try {
    		return (Object)valOps.get(key);
    	} catch (Exception e) {
    		return null;
    	}
    }
    public void removeTradingAccount(Long id) {
    	String key = TRADING_ACCOUNT_PREFIX+id;
    	redisTemplate.delete(key);
    }
    
    public void saveExchangeAccount(Long id, Object account) {
    	String key = EXCHANGE_ACCOUNT_PREFIX+id;
    	valOps.set(key,account,1,TimeUnit.DAYS);
    }
    public Object getExchangeAccount(Long id) {
    	String key = EXCHANGE_ACCOUNT_PREFIX+id;
    	try {
    		return (Object)valOps.get(key);
    	} catch (Exception e) {
    		return null;
    	}
    }
    public void removeExchangeAccount(Long id) {
    	String key = EXCHANGE_ACCOUNT_PREFIX+id;
    	redisTemplate.delete(key);
    }
    
    public void saveFollowAccount(Long id, Object account) {
//    	String key = FOLLOW_ACCOUNT_PREFIX+id;
//    	valOps.set(key,account,1,TimeUnit.DAYS);
    }
    public Object getFollowAccount(Long id) {
    	String key = FOLLOW_ACCOUNT_PREFIX+id;
    	try {
    		return valOps.get(key);
    	} catch (Exception e) {
    		return null;
    	}
    }
    public void removeFollowAccount(Long id) {
//    	String key = FOLLOW_ACCOUNT_PREFIX+id;
//    	redisTemplate.delete(key);
    }
    
    public void saveSpecialTraderStatus(String symbol, Long memberId, int status) {
    	String key = SPECAIL_TRADER_STATUS_PREFIX+symbol+":"+memberId;
    	valOps.set(key,status,30,TimeUnit.DAYS);
    }
    public Integer getSpecialTraderStatus(String symbol, Long memberId) {
    	String key = SPECAIL_TRADER_STATUS_PREFIX+symbol+":"+memberId;
    	try {
    		Object val = valOps.get(key);
    		if(val == null)
    			return 0;
    		return (Integer)valOps.get(key);
    	} catch (Exception e) {
    		return 0;
    	}
    }
    public void removeSpecialTraderStatus(String symbol, Long memberId) {
    	String key = SPECAIL_TRADER_STATUS_PREFIX+symbol+":"+memberId;
    	redisTemplate.delete(key);
    }
    
    public void saveFundingRateMap(Map biasMap) {
    	String key = FUNDING_RATE_PREFIX;
    	valOps.set(key,JSON.toJSONString(biasMap),7,TimeUnit.DAYS);
    }
    public Map<Integer, BigDecimal> getFundingRateMap() {
    	String key = FUNDING_RATE_PREFIX;
		try {
	    	Object biasMap = valOps.get(key);
	    	if(biasMap!=null) {
	    		String json = biasMap.toString();
	    		return (Map<Integer, BigDecimal>)JSON.parseObject(json, Map.class);
	    		
	    	} else {
	    		return null;
	    	}
		} catch (Exception e) {
			return null;
		}
    }
    public void removeBackLength() {
    	String key = BACK_LENGTH_PREFIX;
    	log.info("【开仓前移】删除Redis key, key={}", key);
    	try {
    		redisTemplate.delete(key);
    		log.info("【开仓前移】删除Redis key成功, key={}", key);
    	} catch (Exception e) {
    		log.error("【开仓前移】删除Redis key失败, key={}, error={}", key, e.getMessage(), e);
    	}
    }
    
    public void saveBackLength(int backLength) {
    	String key = BACK_LENGTH_PREFIX;
    	log.info("【开仓前移】保存到Redis, key={}, backLength={}, expire=7天", key, backLength);
    	try {
    		valOps.set(key, backLength, 7, TimeUnit.DAYS);
    		// 验证保存是否成功
    		Object savedValue = valOps.get(key);
    		log.info("【开仓前移】保存到Redis成功, key={}, backLength={}, 验证值={}", key, backLength, savedValue);
    	} catch (Exception e) {
    		log.error("【开仓前移】保存到Redis失败, key={}, backLength={}, error={}", key, backLength, e.getMessage(), e);
    		throw e;
    	}
    }
    
    public Integer getBackLength() {
    	String key = BACK_LENGTH_PREFIX;
    	log.debug("【开仓前移】从Redis获取, key={}", key);
		try {
			Object val = valOps.get(key);
    		if(val == null) {
    			log.debug("【开仓前移】Redis中不存在该key, key={}, 返回默认值0", key);
    			return 0;
    		}
    		Integer result = (Integer)valOps.get(key);
    		log.debug("【开仓前移】从Redis获取成功, key={}, backLength={}", key, result);
    		return result;
		} catch (Exception e) {
			log.error("【开仓前移】从Redis获取失败, key={}, error={}, 返回默认值0", key, e.getMessage(), e);
			return 0;
		}
    }
    public void removeFundingRateMap() {
    	String key = FUNDING_RATE_PREFIX;
    	redisTemplate.delete(key);
    }
    
    public void saveSysConfig(SysConfig sysConfig) {
    	String key = SYS_CONFIG_PREFIX;
    	valOps.set(key,JSON.toJSONString(sysConfig),7,TimeUnit.DAYS);
    }
    public SysConfig getSysConfig() {
    	String key = SYS_CONFIG_PREFIX;
		try {
	    	Object raw = valOps.get(key);
	    	if(raw == null) {
	    		return null;
	    	}

	    	String json;
	    	if(raw instanceof byte[]) {
	    		json = new String((byte[]) raw, "UTF-8");
	    	} else {
	    		json = raw.toString();
	    	}

	    	SysConfig result = JSON.parseObject(json, SysConfig.class);
	    	return result;

		} catch (Exception e) {
			return null;
		}
    }
    public void removeSysConfig() {
    	String key = SYS_CONFIG_PREFIX;
    	redisTemplate.delete(key);
    }
    
    public void saveAppRevision(AppRevision appRevision) {
    	String key = APP_REVISION_PREFIX + appRevision.getPlatform();
    	valOps.set(key,JSON.toJSONString(appRevision),7,TimeUnit.DAYS);
    }
    public AppRevision getAppRevision(Platform platform) {
    	String key = APP_REVISION_PREFIX + platform;
		try {
	    	Object sysConfig = valOps.get(key);
	    	if(sysConfig!=null) {
	    		String json = sysConfig.toString();
	    		return JSON.parseObject(json, AppRevision.class);
	    		
	    	} else {
	    		return null;
	    	}
		} catch (Exception e) {
			return null;
		}
    }
    
    public void saveBiasMap(Map biasMap) {
    	String key = SYMBOL_PRICE_BIAS_PREFIX;
    	valOps.set(key,JSON.toJSONString(biasMap),7,TimeUnit.DAYS);
    }
    public Map getBiasMap() {
    	String key = SYMBOL_PRICE_BIAS_PREFIX;
		try {
	    	Object biasMap = valOps.get(key);
	    	if(biasMap!=null) {
	    		String json = biasMap.toString();
	    		return JSON.parseObject(json, Map.class);
	    		
	    	} else {
	    		return null;
	    	}
		} catch (Exception e) {
			return null;
		}
    }
    public void removeBiasMap() {
    	String key = SYMBOL_PRICE_BIAS_PREFIX;
    	redisTemplate.delete(key);
    }
    
    public void saveBlastAlarm(Long memberId) {
    	String key = BLAST_ALARM_PREFIX + memberId;
    	valOps.set(key,1,24,TimeUnit.HOURS);
    }
    public void clearBlastAlarm(Long memberId) {
        redisTemplate.delete(BLAST_ALARM_PREFIX + memberId);
    }
    public void clearFollowBlastAlarm(Long memberId) {
        redisTemplate.delete(BLAST_ALARM_FOLLOW_PREFIX + memberId);
    }
    public boolean getBlastAlarm(Long memberId) {
    	String key = BLAST_ALARM_PREFIX + memberId;
		try {
	    	Object blastAlarm = valOps.get(key);
	    	if(blastAlarm!=null) {
	    		return false;
	    		
	    	} else {
	    		saveBlastAlarm(memberId);
	    		return true;
	    	}
		} catch (Exception e) {
			return false;
		}
    }
    
    /**
     * 尝试标记全仓爆仓邮件已发送，24小时内同一用户重复调用返回 false
     *
     * @param memberId 用户ID
     * @return true=可以发送（首次），false=已发过，跳过
     */
    public boolean tryMarkBlastEmail(Long memberId) {
        String key = BLAST_EMAIL_PREFIX + memberId;
        try {
            Object existing = valOps.get(key);
            if (existing != null) {
                return false;
            }
            valOps.set(key, 1, 24, TimeUnit.HOURS);
            return true;
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 清除全仓爆仓邮件去重标记（用户重新开仓后需清除，以便下次爆仓仍能收到通知）
     *
     * @param memberId 用户ID
     */
    public void clearBlastEmail(Long memberId) {
        redisTemplate.delete(BLAST_EMAIL_PREFIX + memberId);
    }

    /**
     * 尝试标记逐仓爆仓邮件已发送，24小时内同一用户重复调用返回 false
     *
     * @param memberId 用户ID
     * @return true=可以发送（首次），false=已发过，跳过
     */
    public boolean tryMarkBlastPositionEmail(Long memberId) {
        String key = BLAST_POSITION_EMAIL_PREFIX + memberId;
        try {
            Object existing = valOps.get(key);
            if (existing != null) {
                return false;
            }
            valOps.set(key, 1, 24, TimeUnit.HOURS);
            return true;
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 清除逐仓爆仓邮件去重标记
     *
     * @param memberId 用户ID
     */
    public void clearBlastPositionEmail(Long memberId) {
        redisTemplate.delete(BLAST_POSITION_EMAIL_PREFIX + memberId);
    }

    public void saveFollowBlastAlarm(Long memberId) {
    	String key = BLAST_ALARM_FOLLOW_PREFIX + memberId;
    	valOps.set(key,1,24,TimeUnit.HOURS);
    }
    public boolean getFollowBlastAlarm(Long memberId) {
    	String key = BLAST_ALARM_FOLLOW_PREFIX + memberId;
		try {
	    	Object blastAlarm = valOps.get(key);
	    	if(blastAlarm!=null) {
	    		return false;
	    		
	    	} else {
	    		saveFollowBlastAlarm(memberId);
	    		return true;
	    	}
		} catch (Exception e) {
			return false;
		}
    }
    
    
    public void saveTokenMember(String token, Member member) {
    	String key = TOKEN_PREFIX+token;
    	valOps.set(key,member,7,TimeUnit.DAYS);
    }
    public Member getTokenMember(String token) {
    	String key = TOKEN_PREFIX+token;
    	return (Member)valOps.get(key);
    }
    public void removeTokenMember(String token) {
    	redisTemplate.delete(token);
    }

    public static final String MEMBER_SESSION_PREFIX = "MEMEBER:SESSION:";
    public void saveSessionId(Long memberId, String sessionId) {
    	String key = MEMBER_SESSION_PREFIX+memberId;
    	valOps.set(key,sessionId,100,TimeUnit.DAYS);
    }
    public String getSessionId(Long memberId) {
    	String key = MEMBER_SESSION_PREFIX+memberId;
    	Object value = valOps.get(key);
    	if(value == null)
    		return "";
    	else
    		return value.toString();
    }
    public void removeSessionByMemberId(Long memberId, String token) {
    	String sessionId = token;
    	if(sessionId==null || sessionId.equals(""))
    		sessionId = this.getSessionId(memberId);
    	//log.info("sessionId={}", sessionId);
    	if(!sessionId.equals("")) {
    		//log.info("sessionId2={}", "spring:session:sessions:"+sessionId);
    		redisTemplate.delete(MEMBER_SESSION_PREFIX+memberId);
    		redisTemplate.delete("spring:session:sessions:"+sessionId);
    		redisTemplate.delete("spring:session:sessions:expires:"+sessionId);
    	}
    }
    public AuthMember getBySessionId(String sessionId) {
    	return (AuthMember)valOps.get("spring:session:sessions:"+sessionId);
    }
    
    private boolean setNX(final String key, final String value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    public boolean lockBySeconds(String key, int seconds){
    	Long nowTime = new Date().getTime();
        Long exprirationTime = nowTime + (seconds * 1000);
        // 通过setNx获取锁
        boolean absent = setNX(key, String.valueOf(exprirationTime));
        if(!absent) {
        	Object res = redisTemplate.opsForValue().get(key);
        	if(res == null) {
        		redisTemplate.delete(key);
        		return setNX(key, String.valueOf(exprirationTime));
        	}
        	Long absentExprirationTime = Long.parseLong(res.toString());
        	if(absentExprirationTime == null || absentExprirationTime<nowTime) {
        		redisTemplate.delete(key);
        		return setNX(key, String.valueOf(exprirationTime));
        	}
        }
        return absent;
    }
    
    public void unlock(String key) {
    	redisTemplate.delete(key);
    }
    
    // ==================== 管理后台 Token ====================

    /** 管理员登录 Token Redis key 前缀，value 为 Admin JSON，过期时间 8 小时 */
    public static final String ADMIN_TOKEN_PREFIX = "ADMIN_TOKEN:";

    /** 管理员登录中间态 key 前缀（验证码通过后、二次验证前），过期时间 5 分钟 */
    public static final String ADMIN_LOGIN_TEMP_PREFIX = "ADMIN_LOGIN_TEMP:";

    /**
     * 保存管理员登录 Token 到 Redis，支持同一账号多设备同时登录
     *
     * @param token 登录 token（UUID）
     * @param admin 管理员对象
     */
    public void saveAdminToken(String token, Admin admin) {
        String key = ADMIN_TOKEN_PREFIX + token;
        valOps.set(key, JSON.toJSONString(admin), 8, TimeUnit.HOURS);
    }

    /**
     * 根据 token 获取管理员对象
     *
     * @param token 登录 token
     * @return Admin 对象，token 不存在或已过期则返回 null
     */
    public Admin getAdminByToken(String token) {
        String key = ADMIN_TOKEN_PREFIX + token;
        Object raw = valOps.get(key);
        if (raw == null) {
            return null;
        }
        return JSON.parseObject(raw.toString(), Admin.class);
    }

    /**
     * 删除管理员登录 Token（退出登录时调用）
     *
     * @param token 登录 token
     */
    public void removeAdminToken(String token) {
        redisTemplate.delete(ADMIN_TOKEN_PREFIX + token);
    }

    /**
     * 保存管理员登录中间态（验证码验证通过后，等待二次验证）
     *
     * @param tempToken 临时 token（UUID）
     * @param admin     管理员对象
     */
    public void saveAdminLoginTemp(String tempToken, Admin admin) {
        String key = ADMIN_LOGIN_TEMP_PREFIX + tempToken;
        valOps.set(key, JSON.toJSONString(admin), 5, TimeUnit.MINUTES);
    }

    /**
     * 获取登录中间态管理员信息
     *
     * @param tempToken 临时 token
     * @return Admin 对象，不存在或已过期则返回 null
     */
    public Admin getAdminLoginTemp(String tempToken) {
        String key = ADMIN_LOGIN_TEMP_PREFIX + tempToken;
        Object raw = valOps.get(key);
        if (raw == null) {
            return null;
        }
        return JSON.parseObject(raw.toString(), Admin.class);
    }

    /**
     * 删除登录中间态（完成登录或登录失败时清理）
     *
     * @param tempToken 临时 token
     */
    public void removeAdminLoginTemp(String tempToken) {
        redisTemplate.delete(ADMIN_LOGIN_TEMP_PREFIX + tempToken);
    }

    // ==================== 用户端 Member Token ====================

    /** 用户登录 Token Redis key 前缀，value 为 AuthMember JSON，过期时间 7 天 */
    public static final String MEMBER_TOKEN_PREFIX = "MEMBER_TOKEN:";

    /**
     * 保存用户登录 Token 到 Redis，支持多设备同时登录
     *
     * @param token      登录 token（UUID）
     * @param authMember 用户认证对象
     */
    public void saveMemberToken(String token, AuthMember authMember) {
        String key = MEMBER_TOKEN_PREFIX + token;
        valOps.set(key, JSON.toJSONString(authMember), 7, TimeUnit.DAYS);
    }

    /**
     * 根据 token 获取用户认证对象
     *
     * @param token 登录 token
     * @return AuthMember 对象，token 不存在或已过期则返回 null
     */
    public AuthMember getMemberByToken(String token) {
        String key = MEMBER_TOKEN_PREFIX + token;
        Object raw = valOps.get(key);
        if (raw == null) {
            return null;
        }
        return JSON.parseObject(raw.toString(), AuthMember.class);
    }

    /**
     * 删除用户登录 Token（退出登录时调用）
     *
     * @param token 登录 token
     */
    public void removeMemberToken(String token) {
        redisTemplate.delete(MEMBER_TOKEN_PREFIX + token);
    }

    /** 现货最新价格 Redis Hash key */
    public static final String SPOT_LATEST_PRICE_KEY = "ExchangeNew2:latestPrice";

    /**
     * 从 Redis 读取指定币种对 USDT 的现货最新价格
     * Hash key：ExchangeNew2:latestPrice，field 格式：coinUnit 全小写 + "usdt"（如 ibitusdt）
     *
     * @param coinUnit 币种单位，如 IBIT、BTC（大小写不敏感）
     * @return 价格；USDT 自身返回 1；无行情或异常返回 null
     */
    public java.math.BigDecimal getSpotPriceUsdt(String coinUnit) {
        if (coinUnit == null || coinUnit.trim().isEmpty()) {
            return null;
        }
        if ("USDT".equalsIgnoreCase(coinUnit)) {
            return java.math.BigDecimal.ONE;
        }
        try {
            // field 格式：ibitusdt（全小写，直接拼接 usdt）
            String field = coinUnit.trim().toLowerCase() + "usdt";
            Object val = redisTemplate.opsForHash().get(SPOT_LATEST_PRICE_KEY, field);
            if (val != null) {
                java.math.BigDecimal price = new java.math.BigDecimal(val.toString());
                if (price.compareTo(java.math.BigDecimal.ZERO) > 0) {
                    return price;
                }
            }
        } catch (Exception e) {
            log.warn("[RedisManager] 读取现货价格失败 coinUnit={}, error={}", coinUnit, e.getMessage());
        }
        return null;
    }

    public static final String WITHDRAW_PREFIX = "WITHDRAW_LOCK:";
    public boolean lockWithdraw(Long id) {
    	String key = WITHDRAW_PREFIX + id;
    	return this.lockBySeconds(key, 3);
    }
    public void unlockWithdraw(Long id) {
    	String key = WITHDRAW_PREFIX + id;
    	this.unlock(key);
    }
    
    public static final String TRANSFER_PREFIX = "TRANSFER_LOCK:";
    public boolean lockTransfer(Long id) {
    	String key = TRANSFER_PREFIX + id;
    	return this.lockBySeconds(key, 3);
    }
    public void unlockTransfer(Long id) {
    	String key = TRANSFER_PREFIX + id;
    	this.unlock(key);
    }

}
