package study.orderflow;

import java.math.BigDecimal;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;
import java.util.HexFormat;
import java.util.Map;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.jdbc.core.JdbcTemplate;

/// 合成签发器与建表夹具。所有密钥/数据均为本实验合成，
/// 不代表生产凭据；HS256 secret 与 RW 签名密钥在测试 JVM 内生成。
public final class LabFixtures {

    public static final String TENANT_T1 = "t1";
    public static final String TENANT_T2 = "t2";
    public static final String AUDIENCE = "orders-api";
    public static final String ISSUER = "https://as.orderflow.local";

    private LabFixtures() {
    }

    public static SecretKey hs256Key(String secretBase64) {
        return new SecretKeySpec(Base64.getDecoder().decode(secretBase64), "HmacSHA256");
    }

    public static String randomHs256Secret() {
        byte[] bytes = new byte[32];
        new java.security.SecureRandom().nextBytes(bytes);
        return Base64.getEncoder().encodeToString(bytes);
    }

    /** 与 OrderService.sha256 同算法，供测试构造 request_hash 期望。 */
    public static String sha256(String text) {
        try {
            return HexFormat.of().formatHex(
                    MessageDigest.getInstance("SHA-256").digest(text.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    /** HS256 签发合成 token；claims 由调用方给全（issuer/aud/exp/tenant/scope）。 */
    public static String mintHs256(SecretKey key, Map<String, Object> claims) {
        com.nimbusds.jose.JWSSigner signer;
        try {
            signer = new com.nimbusds.jose.crypto.MACSigner(key);
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        var claimsSet = new com.nimbusds.jwt.JWTClaimsSet.Builder();
        for (var entry : claims.entrySet()) {
            if ("exp".equals(entry.getKey())) {
                claimsSet.expirationTime((Date) entry.getValue());
            } else {
                claimsSet.claim(entry.getKey(), entry.getValue());
            }
        }
        try {
            var token = new com.nimbusds.jwt.SignedJWT(
                    new com.nimbusds.jose.JWSHeader.Builder(com.nimbusds.jose.JWSAlgorithm.HS256).build(),
                    claimsSet.build());
            token.sign(signer);
            return token.serialize();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    public static String validToken(SecretKey key, String tenant, String scope, Instant now) {
        return mintHs256(key, Map.of(
                "iss", ISSUER,
                "aud", AUDIENCE,
                "sub", tenant,
                "tenant", tenant,
                "scope", scope,
                "exp", Date.from(now.plusSeconds(300))));
    }

    public static String expiredToken(SecretKey key, String tenant, Instant now) {
        return mintHs256(key, Map.of(
                "iss", ISSUER,
                "aud", AUDIENCE,
                "sub", tenant,
                "tenant", tenant,
                "scope", "orders.read orders.write",
                "exp", Date.from(now.minusSeconds(60))));
    }

    public static String wrongIssuerToken(SecretKey key, String tenant, Instant now) {
        return mintHs256(key, Map.of(
                "iss", "https://evil.attacker.local",
                "aud", AUDIENCE,
                "sub", tenant,
                "tenant", tenant,
                "scope", "orders.read orders.write",
                "exp", Date.from(now.plusSeconds(300))));
    }

    public static String wrongAudienceToken(SecretKey key, String tenant, Instant now) {
        return mintHs256(key, Map.of(
                "iss", ISSUER,
                "aud", "other-api",
                "sub", tenant,
                "tenant", tenant,
                "scope", "orders.read orders.write",
                "exp", Date.from(now.plusSeconds(300))));
    }

    public static final String SCHEMA = """
            CREATE TABLE stock (
              id BIGINT PRIMARY KEY AUTO_INCREMENT,
              tenant VARCHAR(32) NOT NULL,
              sku VARCHAR(64) NOT NULL,
              available INT NOT NULL,
              unit_price DECIMAL(10,2) NOT NULL,
              version BIGINT NOT NULL DEFAULT 0,
              UNIQUE KEY uk_tenant_sku (tenant, sku)
            ) ENGINE=InnoDB;

            CREATE TABLE orders (
              id BIGINT PRIMARY KEY AUTO_INCREMENT,
              tenant VARCHAR(32) NOT NULL,
              sku VARCHAR(64) NOT NULL,
              quantity INT NOT NULL,
              amount DECIMAL(10,2) NOT NULL,
              status VARCHAR(16) NOT NULL,
              idempotency_key VARCHAR(128) NOT NULL,
              created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
              UNIQUE KEY uk_order_idem (tenant, idempotency_key)
            ) ENGINE=InnoDB;

            CREATE TABLE outbox (
              id BIGINT PRIMARY KEY AUTO_INCREMENT,
              event_id VARCHAR(64) NOT NULL,
              aggregate_id BIGINT NOT NULL,
              aggregate_version BIGINT NOT NULL,
              payload JSON NOT NULL,
              state VARCHAR(16) NOT NULL,
              available_at DATETIME(6) NOT NULL,
              claim_version BIGINT NOT NULL DEFAULT 0,
              claim_id VARCHAR(64) NULL,
              lease_until DATETIME(6) NULL,
              UNIQUE KEY uk_outbox_event (event_id),
              KEY idx_outbox_dispatch (state, available_at)
            ) ENGINE=InnoDB;

            CREATE TABLE inbox (
              consumer_name VARCHAR(64) NOT NULL,
              event_id VARCHAR(64) NOT NULL,
              payload_hash CHAR(64) NOT NULL,
              result_code VARCHAR(32) NOT NULL,
              PRIMARY KEY (consumer_name, event_id)
            ) ENGINE=InnoDB;

            CREATE TABLE notification (
              id BIGINT PRIMARY KEY AUTO_INCREMENT,
              tenant VARCHAR(32) NOT NULL,
              order_id BIGINT NOT NULL,
              event_id VARCHAR(64) NOT NULL,
              state VARCHAR(16) NOT NULL,
              UNIQUE KEY uk_notification (order_id, event_id)
            ) ENGINE=InnoDB;

            CREATE TABLE order_idempotency (
              id BIGINT PRIMARY KEY AUTO_INCREMENT,
              idempotency_key VARCHAR(128) NOT NULL,
              tenant VARCHAR(32) NOT NULL,
              request_hash CHAR(64) NOT NULL,
              order_id BIGINT NULL,
              created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
              UNIQUE KEY uk_idem (tenant, idempotency_key),
              KEY idx_tenant (tenant)
            ) ENGINE=InnoDB;
            """;

    public static void createSchema(JdbcTemplate jdbc) {
        jdbc.execute(SCHEMA);
    }
}
