package com.ffii.core.config;

import javax.validation.Valid;

import org.springdoc.core.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestParam;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;

@Configuration
public class OpenApiConfig {

  private final String AUTH_HEADER_DEVICE_ID;
  private final String AUTH_HEADER_ACCESS_TOKEN;

  public OpenApiConfig(
      @Value("${app.mobile.auth-header.deviceId:X-ATT-DeviceId}") String authHeaderDeviceId,
      @Value("${app.mobile.auth-header.accessToken:X-ATT-Access-Token}") String authHeaderAccessToken) {

    this.AUTH_HEADER_DEVICE_ID = authHeaderDeviceId;
    this.AUTH_HEADER_ACCESS_TOKEN = authHeaderAccessToken;
  }

  @Bean
  public GroupedOpenApi protectedApi() {
    return GroupedOpenApi.builder()
        .group("protected-api")
        .pathsToMatch("/protected/**")
        .addOperationCustomizer((operation, handlerMethod) -> {
          ApiResponses responses = operation.getResponses();

          for (MethodParameter p : handlerMethod.getMethodParameters()) {
            RequestParam rp = p.getParameterAnnotation(RequestParam.class);
            if (rp != null && rp.required()) {
              responses.addApiResponse("400", new ApiResponse().description(HttpStatus.BAD_REQUEST.getReasonPhrase()));
              break;
            } else if (p.getParameterAnnotation(Valid.class) != null
                || p.getParameterAnnotation(Validated.class) != null) {
              responses.addApiResponse("400", new ApiResponse().description(HttpStatus.BAD_REQUEST.getReasonPhrase()));
              break;
            }
          }
          responses.addApiResponse("401", new ApiResponse().description(HttpStatus.UNAUTHORIZED.getReasonPhrase()));
          if (handlerMethod.getMethodAnnotation(PreAuthorize.class) != null) {
            responses.addApiResponse("403", new ApiResponse().description(HttpStatus.FORBIDDEN.getReasonPhrase()));
          }
          return operation;
        })
        .addOpenApiCustomiser(api -> {
          api.addSecurityItem(
              new SecurityRequirement()
                  .addList("deviceId", "mobile")
                  .addList("accessToken", "mobile"));
        })
        .build();
  }

  @Bean
  public GroupedOpenApi publicApi() {
    return GroupedOpenApi.builder()
        .group("public-api")
        .pathsToMatch("/public/**")
        .addOperationCustomizer((operation, handlerMethod) -> {
          ApiResponses responses = operation.getResponses();
          for (MethodParameter p : handlerMethod.getMethodParameters()) {
            RequestParam rp = p.getParameterAnnotation(RequestParam.class);
            if (rp != null && rp.required()) {
              responses.addApiResponse("400", new ApiResponse().description(HttpStatus.BAD_REQUEST.getReasonPhrase()));
              break;
            } else if (p.getParameterAnnotation(Valid.class) != null
                || p.getParameterAnnotation(Validated.class) != null) {
              responses.addApiResponse("400", new ApiResponse().description(HttpStatus.BAD_REQUEST.getReasonPhrase()));
              break;
            }
          }
          return operation;
        })
        .build();
  }

  @Bean
  public OpenAPI openAPI() {
    return new OpenAPI()
        .components(new Components()
            .addSecuritySchemes(
                "deviceId",
                new SecurityScheme()
                    .type(SecurityScheme.Type.APIKEY)
                    .in(SecurityScheme.In.HEADER)
                    .name(this.AUTH_HEADER_DEVICE_ID))
            .addSecuritySchemes(
                "accessToken",
                new SecurityScheme()
                    .type(SecurityScheme.Type.APIKEY)
                    .in(SecurityScheme.In.HEADER)
                    .name(this.AUTH_HEADER_ACCESS_TOKEN)))
        .info(new Info().title("App API")
            .description("application api")
            .version("v0.0.1"));
  }

}
