package cloud.nstart.terravault.ordinis.auth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.JwtDecoders;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;

/**
 * Spring Security: actuator открыт, остальное — либо JWT-required, либо
 * permissive (anonymous → PUBLIC scope в {@link ScopeContext}).
 *
 * <p>JWT verification через OIDC issuer-uri (Keycloak): JWK Set автоматически
 * discover'ится с {@code {issuer}/.well-known/openid-configuration}, ротация
 * ключей бесплатна.
 */
@Configuration
@EnableConfigurationProperties(OrdinisAuthProperties.class)
public class SecurityConfig {

  private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http,
                                                 OrdinisAuthProperties props) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> {
          auth.requestMatchers("/actuator/**").permitAll();
          auth.requestMatchers("/error").permitAll();
          // Build-info endpoint — открыт без auth: UI polling с update-banner
          // должен работать ДО login (anonymous landing → "новая версия"
          // banner появится после Keycloak callback). См. VersionController.
          auth.requestMatchers("/api/v1/version").permitAll();
          // Swagger UI + OpenAPI spec — открыты для интеграторов (Альтум, ...).
          auth.requestMatchers(
              "/swagger-ui.html",
              "/swagger-ui/**",
              "/v3/api-docs",
              "/v3/api-docs/**",
              "/v3/api-docs.yaml"
          ).permitAll();
          if (props.requireAuthentication()) {
            auth.anyRequest().authenticated();
          } else {
            auth.anyRequest().permitAll();
          }
        });

    if (props.enabled()) {
      http.oauth2ResourceServer(rs -> rs.jwt(Customizer.withDefaults()));
    } else {
      log.warn("ordinis.auth.issuer-uri не задан — JWT validation отключён, scope только из query/anonymous");
    }
    return http.build();
  }

  @Bean
  public JwtDecoder jwtDecoder(OrdinisAuthProperties props) {
    if (!props.enabled()) {
      return token -> {
        throw new org.springframework.security.oauth2.jwt.BadJwtException(
            "JWT validation disabled: ordinis.auth.issuer-uri не сконфигурирован");
      };
    }

    NimbusJwtDecoder decoder;
    if (props.jwkSetUri() != null && !props.jwkSetUri().isBlank()) {
      decoder = NimbusJwtDecoder.withJwkSetUri(props.jwkSetUri()).build();
    } else {
      // OIDC discovery: загружает /.well-known/openid-configuration → jwks_uri.
      decoder = (NimbusJwtDecoder) JwtDecoders.fromIssuerLocation(props.issuerUri());
    }

    OAuth2TokenValidator<Jwt> validator = props.issuerUri() != null && !props.issuerUri().isBlank()
        ? JwtValidators.createDefaultWithIssuer(props.issuerUri())
        : JwtValidators.createDefault();

    if (props.audience() != null && !props.audience().isBlank()) {
      validator = new DelegatingOAuth2TokenValidator<>(validator, new AudienceValidator(props.audience()));
    }
    decoder.setJwtValidator(validator);
    return decoder;
  }

  /** Валидирует {@code aud} claim. Keycloak кладёт client_id туда. */
  private record AudienceValidator(String expected) implements OAuth2TokenValidator<Jwt> {
    @Override
    public OAuth2TokenValidatorResult validate(Jwt token) {
      if (token.getAudience() != null && token.getAudience().contains(expected)) {
        return OAuth2TokenValidatorResult.success();
      }
      return OAuth2TokenValidatorResult.failure(
          new OAuth2Error("invalid_audience",
              "JWT aud claim не содержит ожидаемого значения " + expected, null));
    }
  }
}
