package org.gcube.keycloak.avatar.storage.s3;

import java.io.InputStream;

import org.gcube.keycloak.avatar.storage.AvatarStorageProvider;
import org.jboss.logging.Logger;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;

import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.errors.ErrorResponseException;

public class MinioAvatarStorageProvider implements AvatarStorageProvider {

    private static final Logger logger = Logger.getLogger(MinioAvatarStorageProvider.class);

    private static final String AVATAR_FOLDER = "avatar";
    private static final int CHUNK_SIZE = 5242880;

    private final Configuration configuration;

    public MinioAvatarStorageProvider(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public void saveAvatarImage(RealmModel realmModel, UserModel userModel, InputStream input) {
        execute(minioClient -> minioClient
                .putObject(PutObjectArgs.builder().bucket(configuration.rootBucket).object(getAvatarFilePath(
                        realmModel, userModel)).stream(input, -1, CHUNK_SIZE).build()));
    }

    @Override
    public InputStream loadAvatarImage(RealmModel realmModel, UserModel userModel) {
        logger.debug("Loading avatar from S3");
        return execute(new Executor<InputStream>() {
            public InputStream execute(MinioClient minioClient) throws Exception {
                try {
                    return minioClient
                    .getObject(GetObjectArgs.builder().bucket(configuration.rootBucket).object(getAvatarFilePath(
                            realmModel, userModel)).build());
                } catch (ErrorResponseException e) {
                    if (e.response().code() == 404) {
                        logger.debugf("Avatar file not found for user '%s' in realm '%s'", userModel.getUsername(), realmModel.getName());
                        return null;
                    } else {
                        throw e;
                    }
                }
            };
        });
    }

    @Override
    public void deleteAvatarImage(RealmModel realmModel, UserModel userModel) {
        logger.debug("Deeleting avatar from S3");
        execute(minioClient -> {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(configuration.rootBucket)
                    .object(getAvatarFilePath(realmModel, userModel)).build());
            return true;
        });

    }

    @Override
    public void close() {
        // NOOP
    }

    public String getAvatarFilePath(RealmModel realmModel, UserModel userModel) {
        return AVATAR_FOLDER + "/" + realmModel.getName() + "/" + userModel.getUsername();
    }

    public <T> T execute(Executor<T> executor) {
        try {
            MinioClient minioClient = MinioClient.builder()
                    .endpoint(configuration.getServerUrl())
                    .credentials(configuration.getAccessKey(), configuration.getSecretKey())
                    .build();

            return executor.execute(minioClient);
        } catch (Exception e) {
            throw new RuntimeException("Executing operation on S3 persistnce", e);
        }
    }

    public static class Configuration {

        private final String serverUrl;
        private final String accessKey;
        private final String secretKey;
        private final String rootBucket;

        public Configuration(String serverUrl, String accessKey, String secretKey, String rootBucket) {
            this.serverUrl = serverUrl;
            this.accessKey = accessKey;
            this.secretKey = secretKey;
            this.rootBucket = rootBucket;
        }

        public String getServerUrl() {
            return serverUrl;
        }

        public String getAccessKey() {
            return accessKey;
        }

        public String getSecretKey() {
            return secretKey;
        }

        public String getRootBucket() {
            return rootBucket;
        }
    }

    public interface Executor<T> {

        T execute(MinioClient minioClient) throws Exception;

    }
}