package link.at17.mid.tushare.robot.models;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Optional;

import jakarta.annotation.PostConstruct;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import com.alibaba.fastjson2.JSONObject;

import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

@Data
@Accessors(chain=true)
@Slf4j
public abstract class FeishuAppEnv {

    private String appId;
    
    private String appSecret;
    
    private String verificationToken;
    
    private String encryptKey = "";
    
    private String larkHost;

    @Getter(AccessLevel.NONE)
    private transient String tenantAccessToken = null;
    @Getter(AccessLevel.NONE)
    private transient int tenantExpire = 0;
    @Getter(AccessLevel.NONE)
    private transient byte[] keyBs;
    @Getter(AccessLevel.NONE)
    private transient Request accessTokenRequest;
    
    private static MessageDigest sha256Digest = null;
    private static Cipher aesCipher = null;
    private static final String TENANT_ACCESS_TOKEN_URI = "/open-apis/auth/v3/tenant_access_token/internal";

    
    @SuppressWarnings("serial")
    @PostConstruct
    private void init() throws NoSuchAlgorithmException, NoSuchPaddingException {
        sha256Digest = MessageDigest.getInstance("SHA-256");
        aesCipher = Cipher.getInstance("AES/CBC/NOPADDING");
        if (!StringUtils.isEmpty(encryptKey)) {
            keyBs = sha256Digest.digest(encryptKey.getBytes(StandardCharsets.UTF_8));
        }
        accessTokenRequest = new Request.Builder()
            .url(getLarkHost() + TENANT_ACCESS_TOKEN_URI)
            .post(
                RequestBody.create(
                    new JSONObject() {{
                        put("app_id", getAppId());
                        put("app_secret", getAppSecret());
                    }}.toJSONString(),
                    MediaType.parse("application/json; charset=utf-8")))
            .build();
    }
    
    public Optional<String> getTenantAccessToken() {
        int currTs = (int) (System.currentTimeMillis() / 1000);
        if (StringUtils.isEmpty(tenantAccessToken) || currTs + 1800 > tenantExpire) {
            updateTenantAccessToken();
        }
        return Optional.ofNullable(tenantAccessToken);
    }
    
    private void updateTenantAccessToken() {
        OkHttpClient okHttpClient = new OkHttpClient();
        final Call call = okHttpClient.newCall(accessTokenRequest);
        Response resp = null;
        try {
            int tenantExpireTemp = (int) (System.currentTimeMillis() / 1000);
            resp = call.execute();
            int status = resp.code();
            if (status != HttpStatus.OK.value()) {
                throw new Exception("status code " + status);
            }
            JSONObject respJson = JSONObject.parseObject(resp.body().string());
            int code = respJson.getIntValue("code");
            if (code != 0) {
                throw new Exception("code = " + code + ", msg = " + respJson.getString("msg"));
            }
            tenantExpire = tenantExpireTemp + respJson.getIntValue("expire");
            tenantAccessToken = respJson.getString("tenant_access_token");
        } catch (Exception e) {
            log.error("Cannot get tenant access token", e);
        }
        finally {
            if (resp != null) resp.close();
        }
    }
    
    public boolean supportDecryption() {
        return StringUtils.isNotEmpty(encryptKey);
    }
    
    public String decrypt(String encryption) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        if (!supportDecryption() || StringUtils.isBlank(encryption)) {
            return encryption;
        }
        byte[] decode = Base64.getDecoder().decode(encryption);
        byte[] iv = new byte[16];
        System.arraycopy(decode, 0, iv, 0, 16);
        byte[] data = new byte[decode.length - 16];
        System.arraycopy(decode, 16, data, 0, data.length);
        aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBs, "AES"), new IvParameterSpec(iv));
        byte[] r = aesCipher.doFinal(data);
        if (r.length > 0) {
            int p = r.length - 1;
            for (; p >= 0 && r[p] <= 16; p--) {
            }
            if (p != r.length - 1) {
                byte[] rr = new byte[p + 1];
                System.arraycopy(r, 0, rr, 0, p + 1);
                r = rr;
            }
        }
        return new String(r, StandardCharsets.UTF_8);
    }
}
