package com.cameleer.server.core.license;

import org.junit.jupiter.api.Test;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.Signature;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class LicenseValidatorTest {

    private KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
        return kpg.generateKeyPair();
    }

    private String sign(PrivateKey key, String payload) throws Exception {
        Signature signer = Signature.getInstance("Ed25519");
        signer.initSign(key);
        signer.update(payload.getBytes());
        return Base64.getEncoder().encodeToString(signer.sign());
    }

    @Test
    void validate_validLicense_returnsLicenseInfo() throws Exception {
        KeyPair kp = generateKeyPair();
        String publicKeyBase64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
        LicenseValidator validator = new LicenseValidator(publicKeyBase64, "acme");

        Instant expires = Instant.now().plus(365, ChronoUnit.DAYS);
        String payload = """
                {"licenseId":"%s","tenantId":"acme","label":"HIGH","tier":"HIGH","limits":{"max_agents":50,"retention_days":90},"iat":%d,"exp":%d,"gracePeriodDays":7}
                """.formatted(UUID.randomUUID(), Instant.now().getEpochSecond(), expires.getEpochSecond()).trim();
        String signature = sign(kp.getPrivate(), payload);
        String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + signature;

        LicenseInfo info = validator.validate(token);

        assertThat(info.label()).isEqualTo("HIGH");
        assertThat(info.getLimit("max_agents", 0)).isEqualTo(50);
        assertThat(info.isExpired()).isFalse();
        assertThat(info.tenantId()).isEqualTo("acme");
        assertThat(info.gracePeriodDays()).isEqualTo(7);
    }

    @Test
    void validate_expiredLicense_throwsException() throws Exception {
        KeyPair kp = generateKeyPair();
        String publicKeyBase64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
        LicenseValidator validator = new LicenseValidator(publicKeyBase64, "acme");

        Instant past = Instant.now().minus(1, ChronoUnit.DAYS);
        String payload = """
                {"licenseId":"%s","tenantId":"acme","tier":"LOW","limits":{},"iat":%d,"exp":%d}
                """.formatted(UUID.randomUUID(), past.minus(30, ChronoUnit.DAYS).getEpochSecond(), past.getEpochSecond()).trim();
        String signature = sign(kp.getPrivate(), payload);
        String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + signature;

        assertThatThrownBy(() -> validator.validate(token))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("expired");
    }

    @Test
    void validate_tamperedPayload_throwsException() throws Exception {
        KeyPair kp = generateKeyPair();
        String publicKeyBase64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
        LicenseValidator validator = new LicenseValidator(publicKeyBase64, "acme");

        String payload = """
                {"licenseId":"%s","tenantId":"acme","tier":"LOW","limits":{},"iat":0,"exp":9999999999}
                """.formatted(UUID.randomUUID()).trim();
        String signature = sign(kp.getPrivate(), payload);

        // Tamper with payload
        String tampered = payload.replace("LOW", "BUSINESS");
        String token = Base64.getEncoder().encodeToString(tampered.getBytes()) + "." + signature;

        assertThatThrownBy(() -> validator.validate(token))
                .isInstanceOf(SecurityException.class)
                .hasMessageContaining("signature");
    }

    @Test
    void validate_missingTenantId_throws() throws Exception {
        KeyPair kp = generateKeyPair();
        String publicKeyBase64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
        LicenseValidator validator = new LicenseValidator(publicKeyBase64, "acme");

        Instant exp = Instant.now().plus(30, ChronoUnit.DAYS);
        String payload = """
                {"licenseId":"%s","tier":"X","limits":{},"iat":%d,"exp":%d}
                """.formatted(UUID.randomUUID(), Instant.now().getEpochSecond(), exp.getEpochSecond()).trim();
        String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + sign(kp.getPrivate(), payload);

        assertThatThrownBy(() -> validator.validate(token))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("tenantId");
    }

    @Test
    void validate_tenantIdMismatch_throws() throws Exception {
        KeyPair kp = generateKeyPair();
        String publicKeyBase64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
        LicenseValidator validator = new LicenseValidator(publicKeyBase64, "beta");

        Instant exp = Instant.now().plus(30, ChronoUnit.DAYS);
        String payload = """
                {"licenseId":"%s","tenantId":"acme","tier":"X","limits":{},"iat":%d,"exp":%d}
                """.formatted(UUID.randomUUID(), Instant.now().getEpochSecond(), exp.getEpochSecond()).trim();
        String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + sign(kp.getPrivate(), payload);

        assertThatThrownBy(() -> validator.validate(token))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("tenantId");
    }

    @Test
    void validate_missingLicenseId_throws() throws Exception {
        KeyPair kp = generateKeyPair();
        String publicKeyBase64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
        LicenseValidator validator = new LicenseValidator(publicKeyBase64, "acme");

        Instant exp = Instant.now().plus(30, ChronoUnit.DAYS);
        String payload = """
                {"tenantId":"acme","tier":"X","limits":{},"iat":%d,"exp":%d}
                """.formatted(Instant.now().getEpochSecond(), exp.getEpochSecond()).trim();
        String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + sign(kp.getPrivate(), payload);

        assertThatThrownBy(() -> validator.validate(token))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("licenseId");
    }
}
