package com.zioinfo.kintex.webhook;

import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

/**
 * 웹훅 서명 유틸(F099).
 * <ul>
 *   <li><b>인바운드 검증</b>: {@code HMAC-SHA256(secret, rawBody)} 을 hex 로 계산하여 발신자 헤더값과
 *       상수시간 비교({@link #verify}). 헤더는 {@code sha256=<hex>} 또는 bare hex 를 허용.</li>
 *   <li><b>아웃바운드 구독 시크릿</b>: 발송이 disabled(스텁)이므로 원문 재계산이 불필요 → {@code SHA-256(secret)}
 *       hex 해시로 저장({@link #hashSecret}). 원문은 저장·응답 어디에도 남기지 않는다.</li>
 * </ul>
 * 순수 함수(상태 없음) — 서명·검증 단위테스트 대상.
 */
@Component
public class WebhookSignature {

    private static final String HMAC_ALG = "HmacSHA256";
    private static final String PREFIX = "sha256=";

    /** {@code HMAC-SHA256(secret, body)} 를 소문자 hex 로. */
    public String sign(String secret, String body) {
        if (secret == null || body == null) {
            return "";
        }
        try {
            Mac mac = Mac.getInstance(HMAC_ALG);
            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_ALG));
            return toHex(mac.doFinal(body.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            // 알고리즘/키 오류는 검증 실패로 귀결(예외 상세 미전파).
            return "";
        }
    }

    /**
     * 발신자 제공 서명이 {@code secret+body} 로 계산한 HMAC 과 일치하는지 상수시간 비교.
     * {@code sha256=} 접두어는 허용, 대소문자 무시. 값이 비었으면 false.
     */
    public boolean verify(String secret, String body, String provided) {
        if (secret == null || secret.isBlank() || provided == null || provided.isBlank()) {
            return false;
        }
        String given = provided.trim();
        if (given.regionMatches(true, 0, PREFIX, 0, PREFIX.length())) {
            given = given.substring(PREFIX.length());
        }
        String expected = sign(secret, body);
        if (expected.isEmpty()) {
            return false;
        }
        // 상수시간 비교(길이 불일치도 상수시간으로 흡수).
        return MessageDigest.isEqual(
                expected.getBytes(StandardCharsets.UTF_8),
                given.toLowerCase().getBytes(StandardCharsets.UTF_8));
    }

    /** 아웃바운드 구독 시크릿 저장용 {@code SHA-256(secret)} hex(원문 미저장). */
    public String hashSecret(String secret) {
        if (secret == null) {
            return "";
        }
        try {
            byte[] d = MessageDigest.getInstance("SHA-256").digest(secret.getBytes(StandardCharsets.UTF_8));
            return toHex(d);
        } catch (Exception e) {
            throw new IllegalStateException("SHA-256 미지원 환경");
        }
    }

    private static String toHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(Character.forDigit((b >> 4) & 0xF, 16));
            sb.append(Character.forDigit(b & 0xF, 16));
        }
        return sb.toString();
    }
}
