package de.effigenix.infrastructure.security;

import de.effigenix.application.usermanagement.SessionManager;
import de.effigenix.application.usermanagement.dto.SessionToken;
import de.effigenix.domain.usermanagement.User;
import de.effigenix.domain.usermanagement.UserId;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.concurrent.ConcurrentHashMap;

/**
 * JWT-based Session Manager implementation.
 *
 * Token Invalidation:
 * - Uses in-memory blacklist with TTL (token expiration time)
 * - Expired tokens are automatically cleaned up every 5 minutes
 * - For production: consider Redis-based blacklist
 */
@Service
public class JwtSessionManager implements SessionManager {

    private final JwtTokenProvider tokenProvider;

    // Token → expiration instant (for TTL-based cleanup)
    private final ConcurrentHashMap<String, Instant> tokenBlacklist = new ConcurrentHashMap<>();

    public JwtSessionManager(JwtTokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    @Override
    public SessionToken createSession(User user) {
        if (user == null) {
            throw new IllegalArgumentException("User cannot be null");
        }

        String accessToken = tokenProvider.generateAccessToken(
                user.id(), user.username(), user.getAllPermissions(), user.branchId()
        );
        String refreshToken = tokenProvider.generateRefreshToken(user.id());

        return SessionToken.create(accessToken, tokenProvider.getAccessTokenExpiration(), refreshToken);
    }

    @Override
    public UserId validateToken(String token) {
        if (token == null || token.isBlank()) {
            throw new IllegalArgumentException("Token cannot be null or empty");
        }

        if (tokenBlacklist.containsKey(token)) {
            throw new SecurityException("Token has been invalidated (user logged out)");
        }

        try {
            return tokenProvider.extractUserId(token);
        } catch (JwtException e) {
            throw new SecurityException("Invalid or expired JWT token: " + e.getMessage(), e);
        }
    }

    @Override
    public SessionToken refreshSession(String refreshToken) {
        if (refreshToken == null || refreshToken.isBlank()) {
            throw new IllegalArgumentException("Refresh token cannot be null or empty");
        }

        try {
            // Validate refresh token
            Claims claims = tokenProvider.validateToken(refreshToken);
            String type = claims.get("type", String.class);
            if (!"refresh".equals(type)) {
                throw new SecurityException("Not a refresh token");
            }

            UserId userId = UserId.of(claims.getSubject());

            // Note: full implementation with user loading should be done via a dedicated Use Case.
            // This is the infrastructure-level token refresh.
            throw new UnsupportedOperationException(
                    "Session refresh requires user loading from repository. " +
                            "Use the RefreshSession use case instead."
            );
        } catch (JwtException e) {
            throw new SecurityException("Invalid or expired refresh token: " + e.getMessage(), e);
        }
    }

    @Override
    public void invalidateSession(String token) {
        if (token != null && !token.isBlank()) {
            try {
                Claims claims = tokenProvider.validateToken(token);
                Instant expiration = claims.getExpiration().toInstant();
                tokenBlacklist.put(token, expiration);
            } catch (JwtException e) {
                // Token is already invalid/expired, no need to blacklist
            }
        }
    }

    /**
     * Scheduled cleanup of expired tokens from the blacklist.
     * Runs every 5 minutes.
     */
    @Scheduled(fixedRate = 300_000)
    public void cleanupExpiredTokens() {
        Instant now = Instant.now();
        tokenBlacklist.entrySet().removeIf(entry -> entry.getValue().isBefore(now));
    }

    public boolean isTokenBlacklisted(String token) {
        return tokenBlacklist.containsKey(token);
    }

    public void clearBlacklist() {
        tokenBlacklist.clear();
    }
}
