package com.iqser.red.service.persistence.management.v1.processor.migration;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.stereotype.Service;

import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.iqser.red.storage.commons.exception.StorageException;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageClientCache;
import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.storage.commons.service.azure.AzureBlobClient;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.IOUtils;

import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;

@Slf4j
@Service
@RequiredArgsConstructor
@SuppressWarnings("PMD")
public class UncompressedFilesMigrationService {

    private final StorageClientCache storageClientCache;

    private final StorageService storageService;


    @SneakyThrows
    public void migrateUncompressedFiles(String tenant) {

        var client = storageClientCache.getClient(tenant);
        if (client.getS3StorageClient() != null) {

            var keysToMigrate = new ArrayList<String>();
            ListObjectsV2Request listObjects = ListObjectsV2Request.builder().bucket(client.getS3StorageClient().getS3StorageConnection().getBucketName()).maxKeys(100).build();

            var response = client.getS3StorageClient().listObjectsV2Paginator(listObjects);

            var counter = new AtomicInteger();
            for (var listResponse : response) {

                var objects = listResponse.contents();
                for (var object : objects) {

                    counter.incrementAndGet();
                    if (!object.key().toLowerCase(Locale.ROOT).endsWith(".gz")) {
                        log.info("Found key to migrate/compress: {}", object.key());
                        keysToMigrate.add(object.key());
                    }
                }
            }

            log.info("Total files that require compression: {}  / {} ", keysToMigrate.size(), counter.get());

            String tmpdir = Files.createTempDirectory("compressed-migration").toFile().getAbsolutePath();

            for (var key : keysToMigrate) {

                log.info("Migrating key: {}", key);
                var getObjectRequest = GetObjectRequest.builder().bucket(client.getS3StorageClient().getS3StorageConnection().getBucketName()).key(key).build();

                var tempFile = new File(tmpdir, key);
                // in case it was created by a previous migration
                tempFile.mkdirs();
                tempFile.delete();

                client.getS3StorageClient().getObject(getObjectRequest, tempFile.toPath());

                try (var fis = new FileInputStream(tempFile)) {
                    storageService.storeObject(tenant, key, fis);

                    IOUtils.closeQuietly(fis);
                }
                try {
                    tempFile.delete();
                } catch (Exception e) {
                    log.debug("Failed to delete temp file: {}", tempFile.getAbsolutePath());
                }

                var deleteObjectsRequest = DeleteObjectRequest.builder().bucket(client.getS3StorageClient().getS3StorageConnection().getBucketName()).key(key).build();

                try {
                    client.getS3StorageClient().deleteObject(deleteObjectsRequest);
                } catch (NoSuchKeyException e) {
                    throw new StorageObjectDoesNotExist(e);
                }

                log.info("Key: {} migrated successfully", key);
            }

        } else if (client.getAzureBlobClient() != null) {

            var azureClient = client.getAzureBlobClient();
            var blobContainerClient = azureClient.getBlobServiceClient().getBlobContainerClient(client.getAzureBlobClient().getAzureStorageConnection().getContainerName());

            List<String> keysToMigrate = new ArrayList<>();

            var counter = new AtomicInteger();
            for (var item : blobContainerClient.listBlobs()) {

                counter.incrementAndGet();
                if (!item.getName().toLowerCase(Locale.ROOT).endsWith(".gz")) {
                    log.info("Found key to migrate/compress: {}", item.getName());
                    keysToMigrate.add(item.getName());
                }
            }

            log.info("Total files that require compression: {}  / {} ", keysToMigrate.size(), counter.get());

            int attempts = 0;
            do {
                keysToMigrate = migrateBlobKeys(tenant, keysToMigrate, azureClient);
                attempts++;
            } while (!keysToMigrate.isEmpty() && attempts <= 3);

            if (!keysToMigrate.isEmpty()) {
                log.error("Failed to migrate {},", keysToMigrate.size());
            }

        }
    }


    @SneakyThrows
    private List<String> migrateBlobKeys(String tenant, List<String> keysToMigrate, AzureBlobClient azureBlobClient) {

        String tmpdir = Files.createTempDirectory("compressed-migration").toFile().getAbsolutePath();

        var failedKeys = new ArrayList<String>();
        for (var key : keysToMigrate) {

            try {
                var blobContainerClient = azureBlobClient.getBlobServiceClient().getBlobContainerClient(azureBlobClient.getAzureStorageConnection().getContainerName());

                log.info("Migrating key: {}", key);

                var itemClient = blobContainerClient.getBlobClient(key);

                var tempFile = new File(tmpdir, key);
                // in case it was created by a previous migration
                tempFile.mkdirs();
                tempFile.delete();

                itemClient.downloadToFile(tempFile.getAbsolutePath(), true);

                var fis = new FileInputStream(tempFile);
                storageService.storeObject(tenant, key, fis);

                IOUtils.closeQuietly(fis);
                try {
                    tempFile.delete();
                } catch (Exception e) {
                    log.debug("Failed to delete temp file: {}", tempFile.getAbsolutePath());
                }

                try {
                    BlobClient blobClient = blobContainerClient.getBlobClient(key);
                    blobClient.delete();
                } catch (Exception e) {
                    log.debug("Failed to delete item {}", key);
                }

                log.info("Key: {} migrated successfully", key);

            } catch (Exception e) {
                log.error("Failed to migrate {}", key, e);
                failedKeys.add(key);
            }
        }

        return failedKeys;

    }

}
