package io.github.shirainbown.sharelink.common;

import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

/**
 * Issues and consumes one-time access tokens shared by the download and protected-link
 * flows. A token is bound to a scope (e.g. {@code "download"} / {@code "link"}). the
 * resource slug, and the verified email / client ip for auditing. It becomes invalid as
 * soon as it is consumed or its TTL (settings {@code basic.tokenTtlSeconds}) elapses.
 */
@Component
public class OneTimeTokenManager {

    public static final String SCOPE_DOWNLOAD = "download";
    public static final String SCOPE_LINK = "link";

    /**
     * Fallback eviction for never-consumed tokens; the effective expiration is checked
     * against the configured TTL on consumption.
     */
    private static final Duration CACHE_TTL = Duration.ofMinutes(30);

    private final Cache<String, TokenPayload> cache =
        CacheBuilder.newBuilder()
            .expireAfterWrite(CACHE_TTL)
            .maximumSize(10_000)
            .build();

    public String issue(String scope, String slug, String email, String ip) {
        var token = UUID.randomUUID().toString();
        cache.put(token, new TokenPayload(scope, slug, email, ip, Instant.now()));
        return token;
    }

    /**
     * Consumes the token: it is removed from the cache no matter the outcome, so a token
     * can never be used twice.
     *
     * @return the payload when the token exists and has not expired, otherwise
     * {@code null}
     */
    public TokenPayload consume(String token, long ttlSeconds) {
        if (StringUtils.isBlank(token)) {
            return null;
        }
        var payload = cache.getIfPresent(token);
        if (payload == null) {
            return null;
        }
        cache.invalidate(token);
        if (Instant.now().isAfter(payload.issuedAt().plusSeconds(ttlSeconds))) {
            return null;
        }
        return payload;
    }

    public record TokenPayload(String scope, String slug, String email, String ip,
                               Instant issuedAt) {
    }
}
