/*
 *  Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 *     Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License.
 *     A copy of the License is located at
 *
 *         http://aws.amazon.com/apache2.0/
 *
 *     or in the "license" file accompanying this file.
 *     This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and limitations under the License.
 *
 *  Portions copyright Copyright 2004-2011 Oracle Corporation. Copyright (C) 2015 The Project Lombok Authors.
 *  Please see LICENSE.txt for applicable license terms and NOTICE.txt for applicable notices.
 */

import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;
import hudson.FilePath;
import hudson.model.TaskListener;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.digest.DigestUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

import static org.apache.commons.codec.binary.Base64.encodeBase64;

@RequiredArgsConstructor
public class S3DataManager {

    private final AmazonS3Client s3Client;
    private final String s3InputBucket;
    private final String s3InputKey;
    private final String sseAlgorithm;
    private final String localSourcePath;
    private final String workspaceSubdir;

    // if localSourcePath is empty, clones, zips, and uploads the given workspace. Otherwise, uploads the file referred to by localSourcePath.
    // The upload bucket used is this.s3InputBucket and the name of the zip file is this.s3InputKey.
    public UploadToS3Output uploadSourceToS3(TaskListener listener, FilePath workspace) throws Exception {
        CodeBuilderValidation.checkS3SourceUploaderConfig(workspace, s3Client, localSourcePath, workspaceSubdir);

        FilePath localFile;
        String zipFileMD5;
        ObjectMetadata objectMetadata = new ObjectMetadata();

        if(localSourcePath != null && !localSourcePath.isEmpty()) {
            String sourcePath = workspace.child(localSourcePath).getRemote();
            LoggingHelper.log(listener, "Local file to be uploaded to S3: " + sourcePath);

            localFile = new FilePath(workspace, getTempFilePath(sourcePath));
            zipFileMD5 = localFile.act(new LocalSourceCallable(workspace, localSourcePath));
        } else {
            if(workspaceSubdir != null && !workspaceSubdir.isEmpty()) {
                workspace = workspace.child(workspaceSubdir);
            }
            String sourcePath = workspace.getRemote();
            LoggingHelper.log(listener, "Zipping directory to upload to S3: " + sourcePath);

            localFile = new FilePath(workspace, getTempFilePath(sourcePath));
            zipFileMD5 = localFile.act(new ZipSourceCallable(workspace));
        }

        // Add MD5 checksum as S3 Object metadata
        objectMetadata.setContentMD5(zipFileMD5);
        objectMetadata.setContentLength(localFile.length());
        if(sseAlgorithm != null && !sseAlgorithm.isEmpty()) {
            objectMetadata.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
        }

        PutObjectRequest putObjectRequest;
        PutObjectResult putObjectResult = new PutObjectResult();

        try(InputStream zipFileInputStream = localFile.read()) {
            putObjectRequest = new PutObjectRequest(s3InputBucket, s3InputKey, zipFileInputStream, objectMetadata);
            LoggingHelper.log(listener, "Uploading to S3 at location " + putObjectRequest.getBucketName() + "/" + putObjectRequest.getKey() + ". MD5 checksum is " + zipFileMD5);
            putObjectResult = s3Client.putObject(putObjectRequest);
        } catch (SdkClientException e) {
            LoggingHelper.log(listener, "Unexpected exception upon uploading source zip to S3: " + e.getMessage());
        }

        try {
            localFile.delete();
        } catch (IOException e) {
            LoggingHelper.log(listener, "Unexpected exception upon deleting source file: " + e.getMessage());
        }

        return new UploadToS3Output(s3InputBucket + "/" + s3InputKey, putObjectResult.getVersionId());
    }

    private String getTempFilePath(String filePath) {
        return filePath.substring(0, filePath.lastIndexOf(File.separator)+1) + UUID.randomUUID().toString() + "-" + s3InputKey;
    }

    public static String getZipMD5(File zipFile) throws IOException {
        return new String(encodeBase64(DigestUtils.md5(new FileInputStream(zipFile))), Charsets.UTF_8);
    }
}