package de.votian.services.security;

import de.votian.services.dto.UserSessionDto;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class AuthSessionService {

    private final Map<String, SessionState> authSessions = new ConcurrentHashMap<>();
    private final Map<String, PendingTotpState> pendingTotpSessions = new ConcurrentHashMap<>();

    public String createAuthenticatedSession(UserSessionDto user) {
        String token = UUID.randomUUID().toString();
        authSessions.put(token, new SessionState(user, Instant.now().plus(12, ChronoUnit.HOURS)));
        return token;
    }

    public Optional<UserSessionDto> resolveAuthenticatedSession(String token) {
        if (token == null || token.isBlank()) {
            return Optional.empty();
        }
        SessionState state = authSessions.get(token);
        if (state == null || state.expiresAt().isBefore(Instant.now())) {
            authSessions.remove(token);
            return Optional.empty();
        }
        return Optional.of(state.user());
    }

    public void invalidateAuthenticatedSession(String token) {
        if (token != null) {
            authSessions.remove(token);
        }
    }

    public String createPendingTotpSession(UserSessionDto user) {
        String token = UUID.randomUUID().toString();
        pendingTotpSessions.put(token, new PendingTotpState(user, Instant.now().plus(5, ChronoUnit.MINUTES)));
        return token;
    }

    public Optional<UserSessionDto> consumePendingTotpSession(String token) {
        if (token == null || token.isBlank()) {
            return Optional.empty();
        }
        PendingTotpState state = pendingTotpSessions.remove(token);
        if (state == null || state.expiresAt().isBefore(Instant.now())) {
            return Optional.empty();
        }
        return Optional.of(state.user());
    }

    public CleanupResult cleanupExpiredSessions() {
        Instant now = Instant.now();
        int expiredAuthenticated = removeExpiredEntries(authSessions, now);
        int expiredPendingTotp = removeExpiredEntries(pendingTotpSessions, now);
        return new CleanupResult(expiredAuthenticated, expiredPendingTotp);
    }

    public record CleanupResult(int expiredAuthenticatedSessions, int expiredPendingTotpSessions) {
        public int totalExpiredSessions() {
            return expiredAuthenticatedSessions + expiredPendingTotpSessions;
        }
    }

    private <T extends ExpiringState> int removeExpiredEntries(Map<String, T> sessions, Instant now) {
        int removed = 0;
        for (Map.Entry<String, T> entry : sessions.entrySet()) {
            if (entry.getValue().expiresAt().isBefore(now) && sessions.remove(entry.getKey(), entry.getValue())) {
                removed++;
            }
        }
        return removed;
    }

    private sealed interface ExpiringState permits SessionState, PendingTotpState {
        Instant expiresAt();
    }

    private record SessionState(UserSessionDto user, Instant expiresAt) implements ExpiringState { }
    private record PendingTotpState(UserSessionDto user, Instant expiresAt) implements ExpiringState { }
}
