package com.jeeplus.auth.service;

import com.jeeplus.auth.config.OidcProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.stereotype.Service;

@Service
public class OidcTokenVerifier {
    private static final String BEARER_PREFIX = "Bearer ";

    private final OidcProperties properties;
    private volatile JwtDecoder decoder;

    public OidcTokenVerifier(OidcProperties properties) {
        this.properties = properties;
    }

    public Jwt verify(String authorizationHeader) {
        if (!properties.isEnabled()) {
            throw new BadCredentialsException("OIDC login is disabled");
        }
        if (StringUtils.isBlank(authorizationHeader)
                || !authorizationHeader.regionMatches(true, 0, BEARER_PREFIX, 0, BEARER_PREFIX.length())) {
            throw new BadCredentialsException("Missing OIDC bearer token");
        }
        String accessToken = authorizationHeader.substring(BEARER_PREFIX.length()).trim();
        if (accessToken.isEmpty()) {
            throw new BadCredentialsException("Missing OIDC bearer token");
        }
        try {
            return getDecoder().decode(accessToken);
        } catch (JwtException e) {
            throw new BadCredentialsException("Invalid or expired OIDC access token", e);
        }
    }

    private JwtDecoder getDecoder() {
        JwtDecoder current = decoder;
        if (current == null) {
            synchronized (this) {
                current = decoder;
                if (current == null) {
                    current = createDecoder();
                    decoder = current;
                }
            }
        }
        return current;
    }

    private JwtDecoder createDecoder() {
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(properties.getJwkSetUri()).build();
        OAuth2TokenValidator<Jwt> issuerValidator = JwtValidators.createDefaultWithIssuer(properties.getIssuerUri());
        OAuth2TokenValidator<Jwt> audienceValidator = jwt -> {
            if (StringUtils.isBlank(properties.getAudience()) || jwt.getAudience().contains(properties.getAudience())) {
                return OAuth2TokenValidatorResult.success();
            }
            OAuth2Error error = new OAuth2Error("invalid_token",
                    "OIDC token does not contain the required audience", null);
            return OAuth2TokenValidatorResult.failure(error);
        };
        jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(issuerValidator, audienceValidator));
        return jwtDecoder;
    }
}
