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

import com.airlock.iam.base.api.domain.model.mapification.MapifiedValues;
import com.airlock.iam.base.domain.model.mapification.MapifiedValuesBuilder;
import com.airlock.iam.flow.api.domain.model.session.MultiFlowScopedFlowAttribute;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
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.OAuth2ResponseMode;
import com.airlock.iam.oauth2.domain.model.State;
import com.airlock.iam.oauth2.domain.model.acr.OpenIdConnectAcrValue;
import com.airlock.iam.oauth2.domain.model.authentication.OpenIdConnectLoginHint;
import com.airlock.iam.oauth2.domain.model.authorization.OAuth2ResponseType;
import com.airlock.iam.oauth2.domain.model.session.PkceCodeChallenge;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public record OAuth2AuthorizationCodeGrantFlowSessionAttribute(
   AuthorizationServerId authorizationServerId, OAuth2OidcAuthorizationRequestAttributes request, List<OAuth2Scope> scopes, boolean forwardedPromptNone
) implements MultiFlowScopedFlowAttribute<OAuth2AuthorizationCodeGrantFlowSessionAttribute> {
   private static final String AS_ID_KEY = "as_id";
   private static final String CLIENT_ID_KEY = "client_id";
   private static final String REQUEST_ATTRIBUTES_KEY = "request";
   private static final String SCOPES_KEY = "scopes";
   private static final String IS_OIDC_KEY = "isOidc";
   private static final String REQUESTED_REDIRECT_URI_KEY = "uri";
   private static final String EFFECTIVE_REDIRECT_URI_KEY = "effective_uri";
   private static final String STATE_KEY = "state";
   private static final String PKCE_CODE_CHALLENGE_KEY = "pkce";
   private static final String CHALLENGE_KEY = "challenge";
   private static final String CHALLENGE_METHOD_KEY = "method";
   private static final String RESPONSE_TYPE_KEY = "response_type";
   private static final String REQUESTED_SCOPES_KEY = "requested_scopes";
   private static final String RESPONSE_MODE_KEY = "response_mode";
   private static final String ACR_KEY = "acr";
   private static final String NONCE_KEY = "nonce";
   private static final String PROMPT_KEY = "prompt";
   private static final String LOGIN_HINT_KEY = "login_hint";
   private static final String MAX_AGE_KEY = "max_age";
   private static final String CODE_FLOW_KEY = "code_flow";
   private static final String HYBRID_FLOW_KEY = "hybrid_flow";
   private static final String ID_TOKEN_KEY = "id_token";
   private static final String ACCESS_TOKEN_KEY = "token";
   private static final String PROMPT_NONE_KEY = "prompt_none";

   public OAuth2AuthorizationCodeGrantFlowSessionAttribute(
      AuthorizationServerId authorizationServerId, OAuth2OidcAuthorizationRequestAttributes request, List<OAuth2Scope> scopes
   ) {
      this(authorizationServerId, request, scopes, false);
   }

   public OAuth2AuthorizationCodeGrantFlowSessionAttribute mergeWith(OAuth2AuthorizationCodeGrantFlowSessionAttribute newAttribute) {
      return newAttribute;
   }

   @Override
   public MapifiedValues mapify() {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("as_id", this.authorizationServerId.id())
         .put("request", this.mapifyRequestAttributes(this.request))
         .put("scopes", OAuth2Scope.convertToScopeNamesList(this.scopes))
         .put("prompt_none", this.forwardedPromptNone)
         .build();
   }

   public static OAuth2AuthorizationCodeGrantFlowSessionAttribute demapify(MapifiedValues values) {
      return new OAuth2AuthorizationCodeGrantFlowSessionAttribute(
         AuthorizationServerId.authorizationServerId(values.get("as_id")),
         demapifyRequestAttributes(values.getValues("request")),
         OAuth2Scope.convertToScopeList(values.getList("scopes")),
         values.getBoolean("prompt_none")
      );
   }

   private MapifiedValues mapifyRequestAttributes(OAuth2OidcAuthorizationRequestAttributes request) {
      MapifiedValuesBuilder values = this.mapifyOAuth2RequestAttributes(request);
      if (request instanceof OpenIdConnectAuthenticationRequestAttributes oidcRequest) {
         values.putIfNotEmpty("acr", oidcRequest.acrValues().stream().map(OpenIdConnectAcrValue::name).toList())
            .putIfPresent("nonce", oidcRequest.nonce())
            .putIfNotEmpty("prompt", oidcRequest.prompt().stream().map(PromptValue::getParamValue).toList())
            .putIfPresent("login_hint", oidcRequest.loginHint().map(OpenIdConnectLoginHint::getLoginHint))
            .putIfPresent("max_age", oidcRequest.maxAge().map(Duration::getSeconds));
      }

      return values.build();
   }

   private MapifiedValuesBuilder mapifyOAuth2RequestAttributes(OAuth2OidcAuthorizationRequestAttributes request) {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("isOidc", request.isOpenIdConnect())
         .put("client_id", request.clientId().id())
         .putIfPresent("uri", request.requestedRedirectUri().map(URI::toASCIIString))
         .put("effective_uri", request.effectiveRedirectUri().toASCIIString())
         .putIfPresent("state", request.state().map(State::getState))
         .putIfPresent("pkce", request.pkceChallenge().map(OAuth2AuthorizationCodeGrantFlowSessionAttribute::mapifyPkceCodeChallenge))
         .put("response_type", mapifyOAuth2ResponseType(request.responseType()))
         .putIfNotEmpty("requested_scopes", request.requestedScopes().stream().map(OAuth2Scope::name).toList())
         .put("response_mode", request.responseMode().name());
   }

   private static OAuth2OidcAuthorizationRequestAttributes demapifyRequestAttributes(MapifiedValues values) {
      OAuth2ClientId clientId = OAuth2ClientId.oAuth2ClientId(values.get("client_id"));
      Optional<URI> redirectUri = values.<String>getOptional("uri").map(URI::create);
      URI effectiveRedirectUri = URI.create(values.get("effective_uri"));
      Optional<State> state = values.<String>getOptional("state").map(State::state);
      Optional<PkceCodeChallenge> pkceCodeChallenge = values.demapifyOptional(
         "pkce", OAuth2AuthorizationCodeGrantFlowSessionAttribute::demapifyPkceCodeChallenge
      );
      OAuth2ResponseType responseType = values.demapifyValue("response_type", OAuth2AuthorizationCodeGrantFlowSessionAttribute::demapifyOAuth2ResponseType);
      List<OAuth2Scope> requestedScopes = values.<String>getList("requested_scopes").stream().map(OAuth2Scope::of).collect(Collectors.toList());
      OAuth2ResponseMode responseMode = OAuth2ResponseMode.valueOf(values.get("response_mode"));
      OAuth2AuthorizationRequestAttributes oauth2Attributes = new OAuth2AuthorizationRequestAttributes(
         clientId, redirectUri, effectiveRedirectUri, state, pkceCodeChallenge, responseType, requestedScopes, responseMode
      );
      if (!values.getBoolean("isOidc")) {
         return oauth2Attributes;
      }

      List<OpenIdConnectAcrValue> acrValues = values.<Object>getList("acr").stream().map(String.class::cast).map(OpenIdConnectAcrValue::of).toList();
      Optional<String> nonce = values.getOptional("nonce");
      List<PromptValue> prompt = values.<String>getList("prompt").stream().map(PromptValue::fromString).toList();
      Optional<OpenIdConnectLoginHint> loginHint = values.<String>getOptional("login_hint").map(OpenIdConnectLoginHint::new);
      Optional<Duration> maxAge = values.<Long>getOptional("max_age").map(Duration::ofSeconds);
      return new OpenIdConnectAuthenticationRequestAttributes(oauth2Attributes, acrValues, nonce, prompt, loginHint, maxAge);
   }

   private static MapifiedValues mapifyPkceCodeChallenge(PkceCodeChallenge pkceCodeChallenge) {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("challenge", pkceCodeChallenge.getChallenge())
         .put("method", pkceCodeChallenge.getChallengeMethod())
         .build();
   }

   private static PkceCodeChallenge demapifyPkceCodeChallenge(MapifiedValues values) {
      return new PkceCodeChallenge(values.get("challenge"), values.get("method"));
   }

   private static MapifiedValues mapifyOAuth2ResponseType(OAuth2ResponseType oAuth2ResponseType) {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("code_flow", oAuth2ResponseType.impliesCodeFlow())
         .put("hybrid_flow", oAuth2ResponseType.impliesHybridFlow())
         .put("id_token", oAuth2ResponseType.requestsIdToken())
         .put("token", oAuth2ResponseType.requestsAccessToken())
         .put("response_type", oAuth2ResponseType.responseType())
         .build();
   }

   private static OAuth2ResponseType demapifyOAuth2ResponseType(MapifiedValues values) {
      return new OAuth2ResponseType(
         values.<Boolean>get("code_flow"),
         values.<Boolean>get("hybrid_flow"),
         values.<Boolean>get("id_token"),
         values.<Boolean>get("token"),
         values.get("response_type")
      );
   }
}
