const {
  S3Client,
  PutObjectCommand,
  GetObjectCommand,
  ListObjectsV2Command,
  DeleteObjectCommand,
  CopyObjectCommand
} = require("@aws-sdk/client-s3");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("./instanceMgr");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");

const createS3Client = () => {
  const S3Options = {
    region: InstanceRegion(),
    credentials: defaultProvider()
  };

  if (InstanceIsLocalStackEnabled()) {
    S3Options.endpoint = InstanceLocalStackEndpoint();
    S3Options.forcePathStyle = true; // Needed for LocalStack to avoid bucket name as hostname
  }

  const s3Client = new S3Client(S3Options);

  /**
   * Uploads a file to the specified S3 bucket and key.
   */
  const uploadFileToS3 = async ({ bucketName, key, content, contentType }) => {
    const params = {
      Bucket: bucketName,
      Key: key,
      Body: content,
      ContentType: contentType ?? "application/json"
    };
    const command = new PutObjectCommand(params);
    return await s3Client.send(command);
  };

  /**
   * Downloads a file from the specified S3 bucket and key.
   */
  const downloadFileFromS3 = async ({ bucketName, key }) => {
    const params = { Bucket: bucketName, Key: key };
    const command = new GetObjectCommand(params);
    const data = await s3Client.send(command);
    return data.Body;
  };

  /**
   * Lists objects in the specified S3 bucket.
   */
  const listFilesInS3Bucket = async (bucketName, prefix = "") => {
    const params = { Bucket: bucketName, Prefix: prefix };
    const command = new ListObjectsV2Command(params);
    const data = await s3Client.send(command);
    return data.Contents || [];
  };

  /**
   * Deletes a file from the specified S3 bucket and key.
   */
  const deleteFileFromS3 = async ({ bucketName, key }) => {
    const params = { Bucket: bucketName, Key: key };
    const command = new DeleteObjectCommand(params);
    return await s3Client.send(command);
  };

  /**
   * Copies a file within S3 from a source bucket/key to a destination bucket/key.
   */
  const copyFileInS3 = async ({ sourceBucket, sourceKey, destinationBucket, destinationKey }) => {
    const params = {
      CopySource: `/${sourceBucket}/${sourceKey}`,
      Bucket: destinationBucket,
      Key: destinationKey
    };
    const command = new CopyObjectCommand(params);
    return await s3Client.send(command);
  };

  /**
   * Checks if a file exists in the specified S3 bucket and key.
   */
  const fileExistsInS3 = async ({ bucketName, key }) => {
    try {
      await downloadFileFromS3({ bucketName, key });
      return true;
    } catch (error) {
      if (error.name === "NoSuchKey" || error.name === "NotFound") {
        return false;
      }
      throw error;
    }
  };

  const getPresignedUrl = async ({ bucketName, key }) => {
    const command = new PutObjectCommand({
      Bucket: bucketName,
      Key: key,
      StorageClass: "INTELLIGENT_TIERING"
    });
    const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 360 });
    return presignedUrl;
  };

  return {
    uploadFileToS3,
    downloadFileFromS3,
    listFilesInS3Bucket,
    deleteFileFromS3,
    copyFileInS3,
    fileExistsInS3,
    getPresignedUrl,
    ...s3Client
  };
};

module.exports = createS3Client();
