package com.zioinfo.kintex.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * OTP 시크릿 저장용 AES-256-GCM 봉인/해제(계약 §0-3: otp_secret 암호화 저장 필수).
 * <p>포맷 {@code enc:v1:base64(iv(12) || ciphertext || tag(16))}. 접두어가 없으면 평문으로 간주해
 * 그대로 반환한다(암호화 도입 이전 시크릿과의 하위호환·점진 이관). 키는 env 주입만
 * ({@code KINTEX_OTP_ENC_KEY}, base64 32바이트). 미주입 시 개발 전용 파생키를 쓰되 경고 로그를 남긴다.
 * 키·평문 시크릿은 로그/응답에 절대 남기지 않는다.
 */
@Component
public class SecretCipher {

    private static final Logger log = LoggerFactory.getLogger(SecretCipher.class);
    private static final String PREFIX = "enc:v1:";
    private static final String TRANSFORM = "AES/GCM/NoPadding";
    private static final int IV_LEN = 12;
    private static final int TAG_BITS = 128;

    private final SecureRandom random = new SecureRandom();
    private final SecretKeySpec key;

    public SecretCipher(SecurityPolicyProperties props) {
        this.key = resolveKey(props.getOtp().getEncKey());
    }

    /** 평문 시크릿을 봉인(enc:v1:...). null/blank 는 그대로 반환. */
    public String seal(String plain) {
        if (plain == null || plain.isBlank()) {
            return plain;
        }
        try {
            byte[] iv = new byte[IV_LEN];
            random.nextBytes(iv);
            Cipher cipher = Cipher.getInstance(TRANSFORM);
            cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, iv));
            byte[] ct = cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8));
            byte[] out = new byte[iv.length + ct.length];
            System.arraycopy(iv, 0, out, 0, iv.length);
            System.arraycopy(ct, 0, out, iv.length, ct.length);
            return PREFIX + Base64.getEncoder().encodeToString(out);
        } catch (Exception e) {
            throw new IllegalStateException("OTP 시크릿 암호화 실패", e);
        }
    }

    /** 봉인 시크릿을 해제. 접두어가 없으면 평문으로 간주(하위호환). null 은 null. */
    public String open(String stored) {
        if (stored == null || stored.isBlank()) {
            return stored;
        }
        if (!stored.startsWith(PREFIX)) {
            return stored; // 이관 이전 평문
        }
        try {
            byte[] raw = Base64.getDecoder().decode(stored.substring(PREFIX.length()));
            byte[] iv = new byte[IV_LEN];
            System.arraycopy(raw, 0, iv, 0, IV_LEN);
            Cipher cipher = Cipher.getInstance(TRANSFORM);
            cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, iv));
            byte[] pt = cipher.doFinal(raw, IV_LEN, raw.length - IV_LEN);
            return new String(pt, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new IllegalStateException("OTP 시크릿 복호화 실패", e);
        }
    }

    private SecretKeySpec resolveKey(String configured) {
        if (configured != null && !configured.isBlank()) {
            byte[] k = Base64.getDecoder().decode(configured.trim());
            if (k.length != 32) {
                throw new IllegalStateException("KINTEX_OTP_ENC_KEY 는 base64 32바이트(AES-256)여야 합니다.");
            }
            return new SecretKeySpec(k, "AES");
        }
        // 개발 전용 파생키 — 운영에서는 반드시 env 주입. 값은 로그에 남기지 않는다.
        log.warn("KINTEX_OTP_ENC_KEY 미주입 → 개발 전용 파생키 사용(운영 배포 전 반드시 주입 필요)");
        try {
            byte[] k = MessageDigest.getInstance("SHA-256")
                    .digest("kintex-dev-otp-key-do-not-use-in-prod".getBytes(StandardCharsets.UTF_8));
            return new SecretKeySpec(k, "AES");
        } catch (Exception e) {
            throw new IllegalStateException("개발 파생키 생성 실패", e);
        }
    }
}
