package io.cameleer.server.app.web;

import org.junit.jupiter.api.Test;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.UUID;

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

class ArtifactDownloadTokenSignerTest {

    private final String secret = "test-secret-do-not-use";
    private final Instant now = Instant.parse("2026-04-27T10:00:00Z");
    private final Clock clock = Clock.fixed(now, ZoneOffset.UTC);

    @Test
    void signedTokenVerifiesWithinTtl() {
        var signer = new ArtifactDownloadTokenSigner(secret, clock);
        UUID id = UUID.randomUUID();
        var token = signer.sign(id, Duration.ofMinutes(5));
        assertThat(signer.verify(id, token.exp(), token.sig())).isTrue();
    }

    @Test
    void rejectsTamperedSignature() {
        var signer = new ArtifactDownloadTokenSigner(secret, clock);
        UUID id = UUID.randomUUID();
        var token = signer.sign(id, Duration.ofMinutes(5));
        assertThat(signer.verify(id, token.exp(), token.sig() + "x")).isFalse();
    }

    @Test
    void rejectsMismatchedAppVersionId() {
        var signer = new ArtifactDownloadTokenSigner(secret, clock);
        UUID id = UUID.randomUUID();
        var token = signer.sign(id, Duration.ofMinutes(5));
        assertThat(signer.verify(UUID.randomUUID(), token.exp(), token.sig())).isFalse();
    }

    @Test
    void rejectsExpired() {
        var signer = new ArtifactDownloadTokenSigner(secret, clock);
        UUID id = UUID.randomUUID();
        long pastExp = now.minusSeconds(1).getEpochSecond();
        String sig = signer.signRaw(id, pastExp);
        assertThat(signer.verify(id, pastExp, sig)).isFalse();
    }

    @Test
    void rejectsNullOrBlankSecret() {
        assertThatThrownBy(() -> new ArtifactDownloadTokenSigner(null, clock))
                .isInstanceOf(IllegalArgumentException.class);
        assertThatThrownBy(() -> new ArtifactDownloadTokenSigner("", clock))
                .isInstanceOf(IllegalArgumentException.class);
        assertThatThrownBy(() -> new ArtifactDownloadTokenSigner("   ", clock))
                .isInstanceOf(IllegalArgumentException.class);
    }

    /** Forces the constant-time-compare branch by flipping one char at the same length —
     * exercises {@code MessageDigest.isEqual}'s byte-by-byte path, not the length short-circuit. */
    @Test
    void rejectsSameLengthTamperedSignature() {
        var signer = new ArtifactDownloadTokenSigner(secret, clock);
        UUID id = UUID.randomUUID();
        var token = signer.sign(id, Duration.ofMinutes(5));
        char last = token.sig().charAt(token.sig().length() - 1);
        char swapped = last == 'A' ? 'B' : 'A';
        String tampered = token.sig().substring(0, token.sig().length() - 1) + swapped;
        assertThat(tampered).hasSameSizeAs(token.sig());
        assertThat(signer.verify(id, token.exp(), tampered)).isFalse();
    }
}
