package com.airlock.iam.oauth2.domain.model.codegrant;

import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.airlock.iam.oauth2.domain.model.OAuth2RequestedResponseMode;
import com.airlock.iam.oauth2.domain.model.OAuth2ResponseMode;
import com.airlock.iam.oauth2.domain.model.State;
import com.airlock.iam.oauth2.domain.model.authorization.OAuth2ResponseType;
import com.airlock.iam.oauth2.domain.model.par.OAuth2ParRequestUri;
import com.airlock.iam.oauth2.domain.model.session.PkceCodeChallenge;
import com.airlock.iam.oauth2.domain.model.session.PkceCodeVerifier;
import com.nimbusds.oauth2.sdk.AuthorizationRequest;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.AuthorizationRequest.Builder;
import com.nimbusds.oauth2.sdk.ResponseType.Value;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.Identifier;
import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
import com.nimbusds.oauth2.sdk.util.URLUtils;
import com.nimbusds.openid.connect.sdk.Prompt;
import jakarta.annotation.Nullable;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

public class OAuth2AuthorizationRequest implements OAuth2OidcAuthorizationRequest {
   private final AuthorizationRequest request;

   protected OAuth2AuthorizationRequest(AuthorizationRequest request) {
      this.request = request;
   }

   public static OAuth2AuthorizationRequest create(
      URI authorizationEndpointURI,
      OAuth2ClientId clientId,
      @Nullable URI redirectURI,
      State state,
      List<String> scopeToRequest,
      Optional<PkceCodeVerifier> pkceCodeVerifier
   ) {
      return new OAuth2AuthorizationRequest(
         new Builder(new ResponseType(new String[]{Value.CODE.getValue()}), new ClientID(clientId.id()))
            .redirectionURI(redirectURI)
            .state(com.nimbusds.oauth2.sdk.id.State.parse(state.toString()))
            .endpointURI(authorizationEndpointURI)
            .scope(!scopeToRequest.isEmpty() ? Scope.parse(scopeToRequest) : null)
            .codeChallenge(
               pkceCodeVerifier.map(PkceCodeVerifier::verifier).<CodeVerifier>map(CodeVerifier::new).orElse(null),
               pkceCodeVerifier.isPresent() ? CodeChallengeMethod.S256 : null
            )
            .build()
      );
   }

   public static OAuth2AuthorizationRequest createFromUri(URI authorizationEndpointURI, OAuth2ClientId clientId, URI requestURI) {
      return new OAuth2AuthorizationRequest(new Builder(requestURI, new ClientID(clientId.id())).endpointURI(authorizationEndpointURI).build());
   }

   @Override
   public OAuth2ClientId getClientId() {
      return OAuth2ClientId.oAuth2ClientId(this.request.getClientID().getValue());
   }

   @Override
   public List<OAuth2Scope> getRequestedScopes() {
      return Stream.ofNullable(this.request.getScope()).map(Scope::toStringList).flatMap(Collection::stream).distinct().map(OAuth2Scope::of).toList();
   }

   @Override
   public Optional<OAuth2ResponseType> getResponseType() {
      return Optional.ofNullable(this.request.getResponseType())
         .map(
            responseType -> OAuth2ResponseType.builder()
               .impliesCodeFlow(responseType.impliesCodeFlow())
               .impliesHybridFlow(responseType.impliesHybridFlow())
               .responseType(responseType.toString())
               .requestsIdToken(responseType.contains(ResponseType.IDTOKEN.toString()))
               .requestsAccessToken(responseType.contains(ResponseType.TOKEN.toString()))
               .build()
         );
   }

   @Override
   public OAuth2ResponseMode getImpliedResponseMode() {
      return new OAuth2RequestedResponseMode(this.request.impliedResponseMode().getValue()).toSupportedResponseMode().orElse(OAuth2ResponseMode.QUERY_STRING);
   }

   @Override
   public Optional<OAuth2RequestedResponseMode> getRequestedResponseMode() {
      return Optional.ofNullable(this.request.getResponseMode()).map(responseMode -> new OAuth2RequestedResponseMode(responseMode.getValue()));
   }

   @Override
   public Optional<PkceCodeChallenge> getPkceChallenge() {
      return this.request.getCodeChallenge() != null && this.request.getCodeChallengeMethod() != null
         ? Optional.of(new PkceCodeChallenge(this.request.getCodeChallenge().getValue(), this.request.getCodeChallengeMethod().getValue()))
         : Optional.empty();
   }

   @Override
   public Optional<URI> getRedirectURI() {
      return Optional.ofNullable(this.request.getRedirectionURI());
   }

   @Override
   public Optional<State> getState() {
      return Optional.ofNullable(this.request.getState()).<String>map(Identifier::getValue).map(State::state);
   }

   @Override
   public List<String> getPrompt() {
      return Optional.ofNullable(this.request.getPrompt()).<List<String>>map(Prompt::toStringList).orElseGet(Collections::emptyList);
   }

   @Override
   public URI getAuthorizationEndpointURI() {
      return this.request.getEndpointURI();
   }

   @Override
   public Optional<OAuth2ParRequestUri> getRequestURI() {
      return Optional.ofNullable(this.request.getRequestURI()).map(OAuth2ParRequestUri::new);
   }

   @Override
   public URI toURI() {
      return this.request.toURI();
   }

   @Override
   public Map<String, List<String>> toParameters() {
      return this.request.toParameters();
   }

   @Override
   public String toQueryParametersString() {
      return URLUtils.serializeParameters(this.request.toParameters());
   }
}
