package cclotus.weixin.gateway.service.impl;

import cclotus.weixin.gateway.WeixinConfigure;
import cclotus.weixin.gateway.service.CacheService;
import cclotus.weixin.gateway.service.TokenService;
import cclotus.weixin.util.WeixinError;
import cclotus.weixin.util.WeixinUtil;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

import static org.springframework.util.Assert.hasText;

@Slf4j
@Service
public class TokenServiceImpl implements TokenService {

    //@Autowired
    private final WeixinConfigure config;

    private final CacheService cache;

    @Autowired
    public TokenServiceImpl(WeixinConfigure config, CacheServiceImpl cache){
    	this.config = config;
    	this.cache = cache;
    }

	@Override
	public void clearToken(String appid) {
		// 清空缓存
		setToken(appid, null, 1);
	}

	@Override
	@NonNull
	public Mono<String> fetchToken(String appid) throws WeixinError {
    	return fetchToken(appid, false);
	}

	@Override
	@NonNull
	public Mono<String> fetchToken(String appid, boolean force) throws WeixinError {
		hasText(appid, "appid is null");

		// 从缓存中获取Token
		String val;
		if(!force){
			val = getToken(appid);
			if(val != null)
				return Mono.just(val);
		}

		// 重新获取Token
		return reqToken(appid);
	}

	@NonNull
	Mono<String> reqToken(String appid) throws WeixinError {
    	log.info("request token for: {}", appid);

    	String secret = config.getSecret().get(appid);
		if (secret == null) {
			log.error("can't find secret configure for appid: {}", appid);
			throw new WeixinError(WeixinError.ERR_APPID_INVALID);
		}

    	return WeixinUtil.getAccessToken(appid, secret).flatMap(token -> {
			if(StringUtil.isNullOrEmpty(token.getErrmsg())){
				setToken(appid, token.getAccess_token(), token.getExpires_in() - 300);
				return Mono.just(token.getAccess_token());
			}
			return Mono.error(new WeixinError(token.getErrcode(), token.getErrmsg()));
		});
    }

	void setToken(String appid,String token,long expire) {
		String key = key(appid);
		this.cache.setCache(key, token, expire);
	}

	String getToken(String appid) {
		String key = key(appid);
		return this.cache.getCache(key);
	}

	@Override
	public Mono<String> fetchTicket(String appid) throws WeixinError {
    	hasText(appid, "appid is null");

    	String val = getTicket(appid);
    	if(val != null)
    		return Mono.just(val);

    	// 重新获取Ticket
    	return reqTicket(appid);
	}

    Mono<String> reqTicket(String appid) throws WeixinError {
    	log.info("request ticket for: {}", appid);

    	return fetchToken(appid,false).flatMap(token -> {
			Assert.notNull(token,"fetch access_token fail!");
			return WeixinUtil.getJsapiTicket(token).flatMap(ticket -> {
				if(StringUtil.isNullOrEmpty(ticket.getErrmsg())){
					setTicket(appid, ticket.getTicket(), ticket.getExpires_in() - 300);
					return Mono.just(ticket.getTicket());
				}
				return Mono.error(new WeixinError(ticket.getErrcode(), ticket.getErrmsg()));
			});
		});
	}
	void setTicket(String appid,String ticket,long expire) {
		String key = key(appid, true);
		this.cache.setCache(key, ticket, expire);
	}

	String getTicket(String appid) {
		String key = key(appid,true);
		return this.cache.getCache(key);
	}


    @Override
	public Mono<String> fetchOpenid(String appid, String code) throws WeixinError {
    	Assert.notNull(appid, "appid is null");
    	Assert.notNull(code, "code is null");

    	String secret = config.getSecret().get(appid);
		if (secret == null) {
			log.error("can't find secret configure for appid: {}", appid);
			throw new WeixinError(WeixinError.ERR_APPID_INVALID);
		}

    	return WeixinUtil.getAuth2Token(appid, secret, code).flatMap(token -> {
			if(token.getErrcode() != 0){
				log.error("fetch openid by code fail: {} - {}", token.getErrcode(), token.getErrmsg());
				return Mono.error(new WeixinError(token.getErrcode(), token.getErrmsg()));
			}

			return Mono.just(token.getOpenid());
		});
	}

	private static String key(String appid){
		return key(appid,false);
	}
	private static String key(String appid, boolean ticket){
		String prefix = ticket ? "wxapi:ticket:" : "wxapi:token:";
		return  prefix + appid;
	}
}
