package io.delphi.qa.auto.awsmfrw.managers.helpers.aws;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import com.google.gson.JsonSyntaxException;
import io.delphi.qa.auto.awsmfrw.common_baby.IsaCommonMethods;
import io.delphi.qa.auto.awsmfrw.common_baby.IsaLogging;
import io.delphi.qa.auto.awsmfrw.common_baby.gson.GenericList;
import io.qameta.allure.Step;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class HlAwsS3 extends BsAwsSdkV1 {

  private final AmazonS3 client;
  private HlAwsS3Asserts asserts;

  public HlAwsS3() {
    client = AmazonS3ClientBuilder.standard().withRegion(region).build();
  }

  public HlAwsS3Asserts asserts() {
    return asserts == null ? asserts = new HlAwsS3Asserts() : asserts;
  }

  @Step("AWS::S3: list objects")
  public Set<S3ObjectSummary> listS3Objects(String bucketName, String prefix) {
    Set<S3ObjectSummary> output = new HashSet<>();
    StringBuilder msg = new StringBuilder("\nAWS::S3: list objects");
    msg.append("\n- bucket:").append(bucketName).append("\n- prefix:").append(prefix);
    try {
      ListObjectsV2Request req =
          new ListObjectsV2Request().withBucketName(bucketName).withPrefix(prefix).withMaxKeys(10);
      ListObjectsV2Result result = null;
      do {
        result = client.listObjectsV2(req);
        for (S3ObjectSummary objectSummary : result.getObjectSummaries()) {
          msg.append("\n- object:")
              .append(objectSummary.getKey())
              .append("\n- size:")
              .append(objectSummary.getSize());
          output.add(objectSummary);
        }
        // If there are more than maxKeys keys in the bucket, get a continuation token
        // and list the next objects.
        String token = result.getNextContinuationToken();
        msg.append("\n- token:").append(token);
        req.setContinuationToken(token);
      } while (result.isTruncated());
    } catch (AmazonServiceException e) {
      msg.append(
              "\n- exception: The call was transmitted successfully, but Amazon S3 couldn't process it,"
                  + " so it returned an error response.")
          .append(e);
      throw new RuntimeException(e);
    } catch (SdkClientException e) {
      msg.append(
              "\n- exception: Amazon S3 couldn't be contacted for a response, or the client"
                  + " couldn't parse the response from Amazon S3.")
          .append(e);
      throw new RuntimeException(e);
    } finally {
      IsaLogging.log(this.getClass(), String.valueOf(msg));
    }
    return output;
  }

  @Step("AWS::S3: list tags")
  public List<Tag> getS3ObjectTags(String bucketName, String keyName) {
    GetObjectTaggingRequest getTaggingRequest = new GetObjectTaggingRequest(bucketName, keyName);
    GetObjectTaggingResult getTagsResult = client.getObjectTagging(getTaggingRequest);
    return getTagsResult.getTagSet();
  }

  @Step("AWS::S3: Get specific file form AWS S3 bucket")
  public <T> List<T> getS3Object(String bucketName, String key, T obj) {
    bucketName = String.format(bucketName, envConfig.env());
    StringBuilder msg = new StringBuilder("\nAWS::S3: Get specific file form AWS S3 bucket");
    S3Object fullObject = null, objectPortion = null, headerOverrideObject = null;
    List<T> loadGenericListFromText = new ArrayList<>();
    try {
      msg.append("\n- try to get files list...");
      fullObject = client.getObject(new GetObjectRequest(bucketName, key));
      loadGenericListFromText.addAll(
          IsaCommonMethods.loadGenericListFromText(
              IsaCommonMethods.inputStream2String(fullObject.getObjectContent()),
              new GenericList<T>(IsaCommonMethods.getGenericClazz(obj))));
      return loadGenericListFromText;
    } catch (JsonSyntaxException e) {
      msg.append("\n- try to get single file...");
      fullObject = client.getObject(new GetObjectRequest(bucketName, key));
      loadGenericListFromText.add(
          IsaCommonMethods.loadGenericClassFromText(
              IsaCommonMethods.inputStream2String(fullObject.getObjectContent()), obj));
      return loadGenericListFromText;
    } catch (AmazonServiceException e) {
      msg.append(
          "\n- exception: The call was transmitted successfully, but Amazon S3 couldn't process it, so it returned an error response.");
      throw e;
    } catch (SdkClientException e) {
      msg.append(
          "\n- exception: SdkClientException: Amazon S3 couldn't be contacted for a response, or the client couldn't parse the response from Amazon S3.");
      throw e;
    } finally {
      msg.append("\n- result size:").append(loadGenericListFromText.size());
      msg.append(
          "\n- finally: To ensure that the network connection doesn't remain open, close any open input streams.");
      IsaLogging.log(this.getClass(), String.valueOf(msg));
      if (fullObject != null) {
        try {
          fullObject.close();
        } catch (IOException e) {
          msg.append("\n exception: object closing failed.");
          e.printStackTrace();
        }
      }
    }
  }

  @Step("AWS::S3: add text object")
  public <T> String addS3Object(String bucketName, String key, T object) {
    StringBuilder msg = new StringBuilder("AWS::S3: add text object");
    try {
      // Upload a text string as a new object
      String toJson = gson.toJson(object);
      msg.append("\n- name: ")
          .append(bucketName)
          .append("\n- key: ")
          .append(key)
          .append("\n- content:\n")
          .append(toJson);
      client.putObject(bucketName, key, toJson);
      return key;
    } catch (AmazonServiceException e) {
      msg.append(
          "\n- exception: The call was transmitted successfully, but Amazon S3 couldn't process\n"
              + "it, so it returned an error response.");
      throw e;
    } catch (SdkClientException e) {
      msg.append(
          "\n- exception: Amazon S3 couldn't be contacted for a response, or the client\n"
              + "couldn't parse the response from Amazon S3.");
      throw e;
    } finally {
      IsaLogging.log(this.getClass(), msg.toString());
    }
  }

  @Step("AWS::S3: add file to bucket")
  public S3ObjectSummary addS3File(String bucketName, String key, File file) {
    StringBuilder msg = new StringBuilder("\nAWS::S3: add file to bucket");
    try {
      // Upload a text string as a new object
      msg.append("\n- name: ").append(bucketName).append("\n- key: ").append(key);
      ObjectMetadata objectMetadata = new ObjectMetadata();
      objectMetadata.setContentLength(file.getTotalSpace());
      objectMetadata.addUserMetadata("info", "For automation needs");
      objectMetadata.addUserMetadata("key", key);
      PutObjectRequest putObjectRequest =
          new PutObjectRequest(bucketName, key, file).withMetadata(objectMetadata);
      PutObjectResult putObjectResult = client.putObject(putObjectRequest);
      S3ObjectSummary s3ObjectSummary = new S3ObjectSummary();
      s3ObjectSummary.setKey(key);
      s3ObjectSummary.setBucketName(bucketName);
      s3ObjectSummary.setETag(putObjectResult.getETag());
      s3ObjectSummary.setSize(putObjectResult.getMetadata().getContentLength());
      return s3ObjectSummary;
    } catch (AmazonServiceException e) {
      msg.append(
          "\n- exception: The call was transmitted successfully, but Amazon S3 couldn't process\n"
              + "it, so it returned an error response.");
      throw e;
    } catch (SdkClientException e) {
      msg.append(
          "\n- exception: Amazon S3 couldn't be contacted for a response, or the client\n"
              + "couldn't parse the response from Amazon S3.");
      throw e;
    } finally {
      IsaLogging.log(this.getClass(), msg.toString());
    }
  }

  @Step("AWS::S3: delete object by key")
  public void deleteS3ObjectByKey(String bucketName, String stringObjKeyName) {
    client.deleteObject(bucketName, stringObjKeyName);
  }

  @Step("AWS::S3: delete object by summary")
  public void deleteS3ObjectSummary(S3ObjectSummary s3ObjectSummary) {
    if (s3ObjectSummary != null) {
      client.deleteObject(s3ObjectSummary.getBucketName(), s3ObjectSummary.getKey());
    }
  }
}
