package com.usky.vpp.client;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.usky.vpp.config.VppUnProperties;
import com.usky.vpp.config.VppUnPropertiesRegistry;
import com.usky.vpp.config.VppUnTenantContext;
import com.usky.vpp.util.VppUnMessageBuilder;
import com.usky.vpp.util.VppUnPayloadHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * UN Token 缓存（按租户维度，TokenRequest 获取，默认约 30 分钟过期）
 */
@Component
public class VppUnTokenHolder {

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

    private final ConcurrentHashMap<Integer, TokenState> tokenStates = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Integer, ReentrantLock> locks = new ConcurrentHashMap<>();

    @Autowired
    private VppUnPropertiesRegistry propertiesRegistry;

    @Autowired
    private VppUnHttpExecutor httpExecutor;

    @Autowired
    private ObjectMapper objectMapper;

    public String getToken() {
        Integer tenantId = VppUnTenantContext.resolveTenantId();
        TokenState state = tokenStates.computeIfAbsent(tenantId, id -> createInitialState(id));
        if (!state.needsRefresh()) {
            return state.token;
        }
        ReentrantLock lock = locks.computeIfAbsent(tenantId, id -> new ReentrantLock());
        lock.lock();
        try {
            state = tokenStates.computeIfAbsent(tenantId, id -> createInitialState(id));
            if (!state.needsRefresh()) {
                return state.token;
            }
            refreshTokenInternal(tenantId);
            return state.token;
        } finally {
            lock.unlock();
        }
    }

    public void invalidate() {
        Integer tenantId = VppUnTenantContext.resolveTenantId();
        ReentrantLock lock = locks.computeIfAbsent(tenantId, id -> new ReentrantLock());
        lock.lock();
        try {
            tokenStates.remove(tenantId);
        } finally {
            lock.unlock();
        }
    }

    public void applyTokenResponse(Map<String, Object> response) {
        Integer tenantId = VppUnTenantContext.resolveTenantId();
        String newToken = VppUnPayloadHelper.getString(response, "token");
        if (!StringUtils.hasText(newToken)) {
            throw new IllegalStateException("TokenResponse 缺少 token");
        }
        VppUnProperties properties = propertiesRegistry.require(tenantId);
        int ttlMinutes = properties.getTokenTtlMinutes() != null ? properties.getTokenTtlMinutes() : 25;
        TokenState state = new TokenState(newToken, Instant.now().plusSeconds(ttlMinutes * 60L));
        tokenStates.put(tenantId, state);
        propertiesRegistry.updateToken(tenantId, newToken);
    }

    private TokenState createInitialState(Integer tenantId) {
        VppUnProperties properties = propertiesRegistry.get(tenantId);
        if (properties != null && StringUtils.hasText(properties.getToken())) {
            int ttlMinutes = properties.getTokenTtlMinutes() != null ? properties.getTokenTtlMinutes() : 25;
            return new TokenState(properties.getToken(), Instant.now().plusSeconds(ttlMinutes * 60L));
        }
        return new TokenState(null, Instant.EPOCH);
    }

    private void refreshTokenInternal(Integer tenantId) {
        VppUnProperties properties = propertiesRegistry.require(tenantId);
        Map<String, Object> request = VppUnMessageBuilder.buildTokenRequest(properties);
        String responseBody = httpExecutor.postRaw("TokenRequest", request, false);
        try {
            Map<String, Object> response = objectMapper.readValue(responseBody, new TypeReference<Map<String, Object>>() {
            });
            Integer code = VppUnPayloadHelper.getInteger(response, "code");
            if (code != null && code != 200) {
                throw new IllegalStateException("TokenRequest 失败: " + response.get("description"));
            }
            applyTokenResponse(response);
            log.info("租户 {} UN Token 已刷新", tenantId);
        } catch (IllegalStateException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new IllegalStateException("TokenResponse 解析失败", ex);
        }
    }

    private static final class TokenState {
        private final String token;
        private final Instant expiresAt;

        private TokenState(String token, Instant expiresAt) {
            this.token = token;
            this.expiresAt = expiresAt;
        }

        private boolean needsRefresh() {
            return !StringUtils.hasText(token) || Instant.now().isAfter(expiresAt);
        }
    }
}
