package study.graphql;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.graphql.execution.ErrorType;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import jakarta.validation.ConstraintViolationException;

/**
 * U50 安全与错误配置：HTTP Basic 保护 /graphql；领域异常统一翻译为 GraphQL errors。
 * 注意：GraphQL 错误不改变 HTTP 状态码（端点仍 200），错误在响应体 errors 里。
 */
@Configuration(proxyBeanMethods = false)
@EnableMethodSecurity
public class SecurityAndErrorsConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(a -> a
                        .requestMatchers("/graphiql", "/graphql/schema").permitAll()
                        .anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults())
                .csrf(csrf -> csrf.ignoringRequestMatchers("/graphql"))
                .build();
    }

    @Bean
    UserDetailsService users() {
        return new InMemoryUserDetailsManager(
                User.withUsername("alice").password("{noop}pw").roles("READER").build(),
                User.withUsername("admin").password("{noop}pw").roles("READER", "ADMIN").build());
    }

    @Bean
    DataFetcherExceptionResolver graphqlExceptionResolver() {
        return new DataFetcherExceptionResolverAdapter() {
            @Override
            protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
                if (ex instanceof BindException || ex instanceof ConstraintViolationException) {
                    return GraphqlErrorBuilder.newError(env)
                            .errorType(ErrorType.BAD_REQUEST)
                            .message("输入不合法：" + ex.getMessage())
                            .build();
                }
                if (ex instanceof AccessDeniedException) {
                    return GraphqlErrorBuilder.newError(env)
                            .errorType(ErrorType.FORBIDDEN)
                            .message("无权访问")
                            .build();
                }
                if (ex instanceof AuthenticationException) {
                    return GraphqlErrorBuilder.newError(env)
                            .errorType(ErrorType.UNAUTHORIZED)
                            .message("未认证")
                            .build();
                }
                return null;
            }
        };
    }

    /** 让 Mutation 只能 admin 调用的方法级规则（@PreAuthorize 在 Controller 上）。 */
    @Bean
    RuntimeWiringConfigurer wiringConfigurer() {
        return wiring -> {
            // 空配置器：展示 Bean 被 boot 自动收集的入口；真实字段映射走注解控制器
        };
    }
}
