/*
 * Copyright 2025 Hirokazu Kobayashi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.idp.server.core.openid.oauth.handler;

import org.idp.server.core.openid.grant_management.AuthorizationGranted;
import org.idp.server.core.openid.grant_management.AuthorizationGrantedRepository;
import org.idp.server.core.openid.oauth.*;
import org.idp.server.core.openid.oauth.clientauthenticator.ClientAuthenticationHandler;
import org.idp.server.core.openid.oauth.configuration.AuthorizationServerConfiguration;
import org.idp.server.core.openid.oauth.configuration.AuthorizationServerConfigurationQueryRepository;
import org.idp.server.core.openid.oauth.configuration.client.ClientConfiguration;
import org.idp.server.core.openid.oauth.configuration.client.ClientConfigurationQueryRepository;
import org.idp.server.core.openid.oauth.context.OAuthRequestContextCreator;
import org.idp.server.core.openid.oauth.context.OAuthRequestContextCreators;
import org.idp.server.core.openid.oauth.factory.RequestObjectFactories;
import org.idp.server.core.openid.oauth.gateway.RequestObjectGateway;
import org.idp.server.core.openid.oauth.io.OAuthPushedRequest;
import org.idp.server.core.openid.oauth.io.OAuthRequest;
import org.idp.server.core.openid.oauth.repository.AuthorizationRequestRepository;
import org.idp.server.core.openid.oauth.request.OAuthRequestParameters;
import org.idp.server.core.openid.oauth.type.oauth.RequestedClientId;
import org.idp.server.core.openid.oauth.validator.OAuthPushedRequestValidator;
import org.idp.server.core.openid.oauth.validator.OAuthRequestValidator;
import org.idp.server.core.openid.oauth.verifier.OAuthRequestVerifier;
import org.idp.server.core.openid.session.OPSession;
import org.idp.server.platform.log.LoggerWrapper;
import org.idp.server.platform.multi_tenancy.tenant.Tenant;

/** OAuthRequestHandler */
public class OAuthRequestHandler {

  LoggerWrapper log = LoggerWrapper.getLogger(OAuthRequestHandler.class);
  OAuthRequestContextCreators oAuthRequestContextCreators;
  OAuthRequestVerifier verifier;
  ClientAuthenticationHandler clientAuthenticationHandler;
  AuthorizationRequestRepository authorizationRequestRepository;
  AuthorizationServerConfigurationQueryRepository authorizationServerConfigurationQueryRepository;
  ClientConfigurationQueryRepository clientConfigurationQueryRepository;
  AuthorizationGrantedRepository grantedRepository;

  public OAuthRequestHandler(
      AuthorizationRequestRepository authorizationRequestRepository,
      AuthorizationServerConfigurationQueryRepository
          authorizationServerConfigurationQueryRepository,
      ClientConfigurationQueryRepository clientConfigurationQueryRepository,
      RequestObjectGateway requestObjectGateway,
      RequestObjectFactories requestObjectFactories,
      AuthorizationGrantedRepository grantedRepository) {
    this.oAuthRequestContextCreators =
        new OAuthRequestContextCreators(
            requestObjectGateway, authorizationRequestRepository, requestObjectFactories);
    this.verifier = new OAuthRequestVerifier();
    this.clientAuthenticationHandler = new ClientAuthenticationHandler();
    this.authorizationRequestRepository = authorizationRequestRepository;
    this.authorizationServerConfigurationQueryRepository =
        authorizationServerConfigurationQueryRepository;
    this.clientConfigurationQueryRepository = clientConfigurationQueryRepository;
    this.grantedRepository = grantedRepository;
  }

  /**
   * Handles a pushed authorization request (PAR).
   *
   * <p>Per RFC 9126 Section 2.1, the PAR endpoint applies the same client authentication rules as
   * the token endpoint. Per RFC 7521 Section 4.2, client_id is resolved with fallback: explicit
   * parameter → Basic Auth → client_assertion JWT iss claim.
   */
  public OAuthPushedRequestContext handlePushedRequest(OAuthPushedRequest pushedRequest) {
    OAuthRequestParameters requestParameters = pushedRequest.toOAuthRequestParameters();
    Tenant tenant = pushedRequest.tenant();
    RequestedClientId clientId = pushedRequest.clientId();

    log.trace(
        "OAuth pushed request started: client={}, response_type={}",
        clientId.value(),
        requestParameters.responseType().value());

    OAuthPushedRequestValidator validator = new OAuthPushedRequestValidator(tenant, pushedRequest);
    validator.validate();

    AuthorizationServerConfiguration authorizationServerConfiguration =
        authorizationServerConfigurationQueryRepository.get(tenant);
    ClientConfiguration clientConfiguration =
        clientConfigurationQueryRepository.get(tenant, clientId);

    OAuthRequestPattern oAuthRequestPattern = requestParameters.analyzePattern();
    OAuthRequestContextCreator oAuthRequestContextCreator =
        oAuthRequestContextCreators.get(oAuthRequestPattern);

    OAuthRequestContext context =
        oAuthRequestContextCreator.create(
            tenant, requestParameters, authorizationServerConfiguration, clientConfiguration, true);

    OAuthPushedRequestContext oAuthPushedRequestContext =
        new OAuthPushedRequestContext(
            context,
            pushedRequest.clientSecretBasic(),
            pushedRequest.toClientCert(),
            pushedRequest.toBackchannelParameters());
    clientAuthenticationHandler.authenticate(oAuthPushedRequestContext);

    verifier.verify(context);

    authorizationRequestRepository.register(tenant, context.authorizationRequest());

    log.info(
        "OAuth pushed request completed: client={}, request_uri={}",
        clientId.value(),
        context.authorizationRequest().identifier().value());

    return oAuthPushedRequestContext;
  }

  public OAuthRequestContext handleRequest(OAuthRequest oAuthRequest) {
    OAuthRequestParameters parameters = oAuthRequest.toParameters();
    Tenant tenant = oAuthRequest.tenant();

    log.trace(
        "OAuth request started: client={}, response_type={}",
        parameters.clientId().value(),
        parameters.responseType().value());
    OAuthRequestValidator validator = new OAuthRequestValidator(tenant, parameters);
    validator.validate();

    AuthorizationServerConfiguration authorizationServerConfiguration =
        authorizationServerConfigurationQueryRepository.get(tenant);
    ClientConfiguration clientConfiguration =
        clientConfigurationQueryRepository.get(tenant, parameters.clientId());

    OAuthRequestPattern oAuthRequestPattern = parameters.analyzePattern();
    OAuthRequestContextCreator oAuthRequestContextCreator =
        oAuthRequestContextCreators.get(oAuthRequestPattern);

    OAuthRequestContext context =
        oAuthRequestContextCreator.create(
            tenant, parameters, authorizationServerConfiguration, clientConfiguration, false);
    verifier.verify(context);

    if (!context.isPushedRequest()) {
      authorizationRequestRepository.register(tenant, context.authorizationRequest());
    }

    // Set OPSession for prompt=none handling
    OPSession opSession = oAuthRequest.opSession();
    if (opSession != null && opSession.exists() && opSession.isActive()) {
      context.setOPSession(opSession);

      AuthorizationGranted authorizationGranted =
          grantedRepository.find(tenant, parameters.clientId(), opSession.user());
      context.setAuthorizationGranted(authorizationGranted);
    }

    return context;
  }
}
