/*!
 * Copyright (c) 2024 PLANKA Software GmbH
 * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
 */

const fs = require('fs');
const {
  CopyObjectCommand,
  DeleteObjectsCommand,
  DeleteObjectCommand,
  GetObjectCommand,
  HeadObjectCommand,
  ListObjectsV2Command,
  PutObjectCommand,
} = require('@aws-sdk/client-s3');

class S3FileManager {
  constructor(client) {
    this.client = client;
  }

  async move(sourceFilePath, filePathSegment, contentType) {
    const command = new PutObjectCommand({
      Bucket: sails.config.custom.s3Bucket,
      Key: filePathSegment,
      Body: fs.createReadStream(sourceFilePath),
      ContentType: contentType,
    });

    await this.client.send(command);
    return null;
  }

  async save(filePathSegment, buffer, contentType) {
    const command = new PutObjectCommand({
      Bucket: sails.config.custom.s3Bucket,
      Key: filePathSegment,
      Body: buffer,
      ContentType: contentType,
    });

    await this.client.send(command);
  }

  async read(filePathSegment) {
    const command = new GetObjectCommand({
      Bucket: sails.config.custom.s3Bucket,
      Key: filePathSegment,
    });

    const result = await this.client.send(command);
    return result.Body;
  }

  async getSizeInBytes(filePathSegment) {
    const headObjectCommand = new HeadObjectCommand({
      Bucket: sails.config.custom.s3Bucket,
      Key: filePathSegment,
    });

    let result;
    try {
      result = await this.client.send(headObjectCommand);
    } catch (error) {
      return null;
    }

    return result.ContentLength;
  }

  async rename(filePathSegment, nextFilePathSegment) {
    const copyObjectCommand = new CopyObjectCommand({
      Bucket: sails.config.custom.s3Bucket,
      Key: nextFilePathSegment,
      CopySource: `${sails.config.custom.s3Bucket}/${filePathSegment}`,
    });

    try {
      await this.client.send(copyObjectCommand);
    } catch (error) {
      return;
    }

    await this.delete(filePathSegment);
  }

  async delete(filePathSegment) {
    const deleteObjectCommand = new DeleteObjectCommand({
      Bucket: sails.config.custom.s3Bucket,
      Key: filePathSegment,
    });

    await this.client.send(deleteObjectCommand);
  }

  async listDir(dirPathSegment, { ContinuationToken } = {}) {
    const listObjectsCommand = new ListObjectsV2Command({
      ContinuationToken,
      Bucket: sails.config.custom.s3Bucket,
      Prefix: `${dirPathSegment}/`,
      Delimiter: '/',
    });

    const result = await this.client.send(listObjectsCommand);

    if (!result.CommonPrefixes) {
      return null;
    }

    const dirnames = result.CommonPrefixes.map(({ Prefix }) =>
      Prefix.slice(dirPathSegment.length + 1, -1),
    );

    if (result.IsTruncated) {
      const otherDirnames = await this.listDir(dirPathSegment, {
        ContinuationToken: result.NextContinuationToken,
      });

      if (otherDirnames) {
        dirnames.push(...otherDirnames);
      }
    }

    return dirnames;
  }

  async renameDir(dirPathSegment, nextDirPathSegment, { ContinuationToken } = {}) {
    const listObjectsCommand = new ListObjectsV2Command({
      ContinuationToken,
      Bucket: sails.config.custom.s3Bucket,
      Prefix: dirPathSegment,
    });

    const result = await this.client.send(listObjectsCommand);

    if (!result.Contents) {
      return;
    }

    // eslint-disable-next-line no-restricted-syntax
    for (const { Key } of result.Contents) {
      // eslint-disable-next-line no-await-in-loop
      await this.rename(Key, `${nextDirPathSegment}/${Key.substring(dirPathSegment.length + 1)}`);
    }

    if (result.IsTruncated) {
      await this.renameDir(dirPathSegment, nextDirPathSegment, {
        ContinuationToken: result.NextContinuationToken,
      });
    }
  }

  async deleteDir(dirPathSegment, { ContinuationToken } = {}) {
    const listObjectsCommand = new ListObjectsV2Command({
      ContinuationToken,
      Bucket: sails.config.custom.s3Bucket,
      Prefix: dirPathSegment,
    });

    const result = await this.client.send(listObjectsCommand);

    if (!result.Contents) {
      return;
    }

    if (result.Contents.length > 0) {
      const deleteObjectsCommand = new DeleteObjectsCommand({
        Bucket: sails.config.custom.s3Bucket,
        Delete: {
          Objects: result.Contents.map(({ Key }) => ({ Key })),
        },
      });

      await this.client.send(deleteObjectsCommand);
    }

    if (result.IsTruncated) {
      await this.deleteDir(dirPathSegment, {
        ContinuationToken: result.NextContinuationToken,
      });
    }
  }

  async isExists(pathSegment) {
    const listObjectsCommand = new ListObjectsV2Command({
      Bucket: sails.config.custom.s3Bucket,
      Prefix: pathSegment,
      MaxKeys: 1,
    });

    const result = await this.client.send(listObjectsCommand);
    return !!result.Contents && result.Contents.length === 1;
  }

  // eslint-disable-next-line class-methods-use-this
  buildUrl(filePathSegment) {
    return `${sails.hooks.s3.getBaseUrl()}/${filePathSegment}`;
  }
}

module.exports = S3FileManager;
