package ru.ibs.socrates.confexporter.external.access.service.impl;

import java.time.Instant;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.ibs.socrates.confexporter.external.access.api.AccessApi;
import ru.ibs.socrates.confexporter.external.access.dto.AccessResponseDto;
import ru.ibs.socrates.confexporter.external.access.properties.AccessTokenProperties;
import ru.ibs.socrates.confexporter.external.access.service.AccessTokenAcquirer;
import ru.ibs.socrates.confexporter.external.access.service.AccessTokenHolder;


/**
 * Сервис получения access токена.
 */
@Service
@Slf4j
public class AccessTokenAcquirerImpl implements AccessTokenAcquirer {

  private static final String GRANT_TYPE = "grant_type";
  private static final String CLIENT_ID = "client_id";
  private static final String CLIENT_SECRET = "client_secret";
  private static final String CLIENT_CREDENTIALS = "client_credentials";

  private final AccessApi accessApi;
  private final AccessTokenHolder accessTokenHolder;
  private final Map<String, String> tokenRequest;
  private final int tokenExpiredDelay;

  /**
   * AccessTokenAcquirerImpl.
   * 
   * @param accessApi
   * @param accessTokenHolder
   * @param accessTokenProperties
   */
  public AccessTokenAcquirerImpl(
      AccessApi accessApi,
      AccessTokenHolder accessTokenHolder,
      AccessTokenProperties accessTokenProperties) {
    this.accessApi = accessApi;
    this.accessTokenHolder = accessTokenHolder;
    this.tokenExpiredDelay = accessTokenProperties.getTokenExpiredDelay();
    this.tokenRequest = buildTokenRequest(
        accessTokenProperties.getClientId(), accessTokenProperties.getClientSecret());
  }

  /**
   * Получить access токен.
   */
  @Override
  public String acquire() {
    try {
      synchronized (this) {
        if (accessTokenHolder.tokenNotExistsOrExpired()) {
          log.info("Access token does not exist or has been expired, acquiring new one ...");
          AccessResponseDto response = accessApi.getToken(tokenRequest).getBody();
          var currentTime = Instant.now();
          accessTokenHolder.setToken(response.getAccessToken(),
              currentTime.plusSeconds(response.getExpiresIn().longValue() - tokenExpiredDelay),
              currentTime);
          log.info("Acquired new access token");
        }
      }
      return accessTokenHolder.getTokenWithType();
    } catch (Exception e) {
      log.error("Error while acquiring access token", e);
      throw new RuntimeException("Error while acquiring Access token", e);
    }
  }

  private Map<String, String> buildTokenRequest(String clientId, String clientSecret) {
    return Map.of(
        GRANT_TYPE, CLIENT_CREDENTIALS,
        CLIENT_ID, clientId,
        CLIENT_SECRET, clientSecret);
  }

}
