package ru.yandex.direct.cloud.iam.service;

import java.io.Closeable;
import java.time.Duration;
import java.time.Instant;

import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.PreDestroy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.TaskScheduler;

import ru.yandex.direct.cloud.iam.CloudApiConnectionInfo;
import ru.yandex.direct.cloud.iam.GrpcIamTokenProvider;
import ru.yandex.direct.cloud.iam.IIamTokenProvider;
import ru.yandex.direct.cloud.iam.ISchedulerWithFixedDelay;
import ru.yandex.direct.config.DirectConfig;

@ParametersAreNonnullByDefault
public class CloudIamTokenProviderService implements IIamTokenProvider, Closeable {
    private static final Logger logger = LoggerFactory.getLogger(CloudIamTokenProviderService.class);

    private GrpcIamTokenProvider iamTokenProvider = null;
    private final LiveResourceOAuthTokenProvider oAuthTokenProvider;
    private final String oAuthTokenUrl;
    private final String iamProviderName;

    /**
     * Создает новый сервис поставщика iam-токенов для Яндекс.Облака
     * @param connectionInfo параметры соединения с API Яндекс-облака
     * @param iamProviderName имя для поставщика iam-токенов облака. Обычно должно совпадать с именем аккаунта Яндекса,
     *                       в обмен на OAUTH-токен которого получается iam-токен
     * @param oAuthTokenUrl путь к файлу, в котором лежит OAUTH-токен, который будет обмениваться на iam-токен облака
     * @param createNewTokensIntervalInSeconds интервал в секундах, как часто обновлять iam-токен (рекомендуется 1 час)
     * @param taskScheduler планировщик задач, в который будет добавлена задачи периодического слежения за
     *                      файлом с OAUTH-токеном и задача по периодическому получению нового iam токена.
     */
    private CloudIamTokenProviderService(
            CloudApiConnectionInfo connectionInfo,
            String iamProviderName,
            String oAuthTokenUrl,
            int createNewTokensIntervalInSeconds,
            TaskScheduler taskScheduler) {

        this.iamProviderName = iamProviderName;
        this.oAuthTokenUrl = oAuthTokenUrl;

        oAuthTokenProvider = new LiveResourceOAuthTokenProvider(
                oAuthTokenUrl, taskScheduler, this::onOAuthTokenChanged);

        ISchedulerWithFixedDelay scheduler =
                (command, initialDelay, delay, timeUnit) ->
                        taskScheduler.scheduleWithFixedDelay(
                                command,
                                Instant.now().plusNanos(timeUnit.toNanos(initialDelay)),
                                Duration.ofNanos(timeUnit.toNanos(delay)));


        iamTokenProvider = new GrpcIamTokenProvider(
                scheduler, connectionInfo, oAuthTokenProvider, createNewTokensIntervalInSeconds);
    }

    private void onOAuthTokenChanged(String oauthToken) {
        if (iamTokenProvider != null) {
            iamTokenProvider.invalidateCurrentIamToken();
        }
        logger.info("OAUTH token for '{}' in url '{}' has changed", iamProviderName, oAuthTokenUrl);
    }

    @Override
    public String getCurrentIamToken() {
        return iamTokenProvider.getCurrentIamToken();
    }

    @Override
    public void invalidateCurrentIamToken() {
        iamTokenProvider.invalidateCurrentIamToken();
    }

    @PreDestroy
    @Override
    public void close() {
        oAuthTokenProvider.close();
        iamTokenProvider.stop();
    }

    /**
     * Создает экземпляр поставщика iam-токенов для АПИ Яндекс.Облака. Параметры соединения с облаком
     * и путь к файлу с OAUTH-токеном берутся из конфига Директа. Этот метод можно использовать для создания
     * синглетона поставщика iam-токенов внутри спринга.
     * @param cloudBranch ветка cloud конфига Директа
     * @param oAuthTokenProviderBranch ветка типа cloud_iam_default.oauth_token_provider конфига Директа
     *                                 (можно передавать другую ветку такого же типа)
     * @param taskScheduler планировщик задач. В него будут добавлена задачи периодического слежения за
     *                      файлом с OAUTH-токеном и задача по периодическому получению нового iam токена.
     * @return Сервис поставщика iam-токенов Яндекс.Облака
     */
    public static CloudIamTokenProviderService create(
            DirectConfig cloudBranch, DirectConfig oAuthTokenProviderBranch, TaskScheduler taskScheduler) {

        String iamProviderName = oAuthTokenProviderBranch.getString("name");
        String oAuthTokenUrl = oAuthTokenProviderBranch.getString("oauth_token_file");
        int createNewTokensIntervalInSeconds =
                (int)oAuthTokenProviderBranch.getDuration("create_new_iam_token_interval").toSeconds();

        CloudApiConnectionInfo connectionInfo = getConnectionInfoFromConfig(cloudBranch.getBranch("api"));

        return new CloudIamTokenProviderService(
                connectionInfo, iamProviderName, oAuthTokenUrl, createNewTokensIntervalInSeconds, taskScheduler);
    }

    /**
     * Парсит параметры соединения с cloud api из конфига Директа
     * @param cloudApiBranch ветка cloud.api конфига Директа (или такого-же типа)
     * @return параметры соединения с cloud-api
     */
    public static CloudApiConnectionInfo getConnectionInfoFromConfig(DirectConfig cloudApiBranch) {
        String apiUrl = cloudApiBranch.getString("url");
        int apiPort = cloudApiBranch.getInt("port");
        String userAgent = cloudApiBranch.getString("user_agent");
        int requestTimeoutInMs = (int)cloudApiBranch.getDuration("request_timeout").toMillis();
        return new CloudApiConnectionInfo(apiUrl, apiPort, userAgent, requestTimeoutInMs);
    }

}
