package com.cloudhandson.ddsbackoffice.service;

import com.cloudhandson.ddsbackoffice.config.DdsMcpIamProperties;
import com.cloudhandson.vpdbackoffice.service.AppException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Obtains and safely caches the short-lived OCI IAM database-access token. */
@Service
public class DdsMcpDatabaseAccessTokenProvider {

  private static final Duration MINIMUM_CACHE_LIFETIME = Duration.ofSeconds(1);
  private static final Logger log = LoggerFactory.getLogger(DdsMcpDatabaseAccessTokenProvider.class);

  private final DdsMcpIamProperties properties;
  private final ObjectMapper objectMapper;
  private final Clock clock;
  private final HttpClient client;
  private volatile CachedToken cached;

  @Autowired
  public DdsMcpDatabaseAccessTokenProvider(
      DdsMcpIamProperties properties,
      ObjectMapper objectMapper,
      Clock clock
  ) {
    this(properties, objectMapper, clock, HttpClient.newBuilder().connectTimeout(properties.timeout()).build());
  }

  DdsMcpDatabaseAccessTokenProvider(
      DdsMcpIamProperties properties,
      ObjectMapper objectMapper,
      Clock clock,
      HttpClient client
  ) {
    this.properties = properties;
    this.objectMapper = objectMapper;
    this.clock = clock;
    this.client = client;
  }

  public String accessToken() {
    if (!properties.configured()) {
      throw unavailable("OCI IAM client, scope 또는 token endpoint가 설정되지 않았습니다.");
    }
    CachedToken current = cached;
    Instant now = clock.instant();
    if (current != null && current.validUntil().isAfter(now)) {
      return current.value();
    }
    synchronized (this) {
      current = cached;
      now = clock.instant();
      if (current != null && current.validUntil().isAfter(now)) {
        return current.value();
      }
      CachedToken refreshed = fetch(now);
      cached = refreshed;
      return refreshed.value();
    }
  }

  private CachedToken fetch(Instant now) {
    try {
      String clientCredentials = encode(properties.clientId()) + ":" + encode(properties.clientSecret());
      String basic = Base64.getEncoder().encodeToString(clientCredentials.getBytes(StandardCharsets.UTF_8));
      String body = "grant_type=client_credentials&scope=" + encode(properties.databaseScope());
      HttpRequest request = HttpRequest.newBuilder(properties.tokenEndpoint())
          .timeout(properties.timeout())
          .header("Authorization", "Basic " + basic)
          .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
          .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
          .build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
      if (response.statusCode() < 200 || response.statusCode() >= 300) {
        throw unavailable("OCI IAM token 발급이 거부되었습니다.");
      }
      JsonNode payload = objectMapper.readTree(response.body());
      String token = payload.path("access_token").asText("").trim();
      long expiresIn = payload.path("expires_in").asLong(0L);
      if (token.isBlank() || expiresIn < 1) {
        throw unavailable("OCI IAM token 응답이 완전하지 않습니다.");
      }
      Duration remaining = Duration.ofSeconds(expiresIn).minus(properties.refreshSkew());
      if (remaining.compareTo(MINIMUM_CACHE_LIFETIME) < 0) {
        remaining = MINIMUM_CACHE_LIFETIME;
      }
      return new CachedToken(token, now.plus(remaining));
    } catch (AppException exception) {
      throw exception;
    } catch (InterruptedException exception) {
      Thread.currentThread().interrupt();
      throw unavailable("OCI IAM token 발급이 중단되었습니다.");
    } catch (Exception exception) {
      log.warn("OCI IAM database-access token request failed: {}", exception.toString());
      throw unavailable("OCI IAM token을 발급할 수 없습니다.");
    }
  }

  private String encode(String value) {
    return URLEncoder.encode(value, StandardCharsets.UTF_8);
  }

  private AppException unavailable(String detail) {
    return new AppException("DDS_CONTEXT_UNAVAILABLE: " + detail);
  }

  private record CachedToken(String value, Instant validUntil) {
  }
}
