import { DefaultAzureCredential } from "@azure/identity";
import {
  AnonymousCredential,
  BlobServiceClient,
  StorageSharedKeyCredential,
} from "@azure/storage-blob";

type StorageBlobConfig = {
  tenant: string;
  client: string;
  secret: string;
  account: string;
  accountKey: string;
  containerName: string;
  connectionString: string;
  sasToken: string;
  serviceBaseURL: string;
  defaultPath: string;
};

type StrapiFile = File & {
  path: string;
  url: string;
  hash: string;
  ext: string;
  buffer: string;
  mime: string;
};

const trimParam = (str: any) =>
  typeof str === "string" ? str.trim() : undefined;

// A helper method used to read a Node.js readable stream into a Buffer
async function streamToBuffer(
  readableStream: NodeJS.ReadableStream
): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    readableStream.on("data", (data: Buffer | string) => {
      chunks.push(data instanceof Buffer ? data : Buffer.from(data));
    });
    readableStream.on("end", () => {
      resolve(Buffer.concat(chunks));
    });
    readableStream.on("error", reject);
  });
}

const createClient = (config: StorageBlobConfig) => {
  const account = <string>trimParam(config.account);
  const accountKey = <string>trimParam(config.accountKey);
  const connStr = trimParam(config.connectionString);

  const tenant = <string>trimParam(config.tenant);
  const client = <string>trimParam(config.client);
  const secret = <string>trimParam(config.secret);

  const sasToken = <string>trimParam(config.sasToken);

  const serviceBaseURL =
    trimParam(config.serviceBaseURL) ||
    `https://${account}.blob.core.windows.net`;

  if (tenant && client && secret && account) {
    //Azure Active Directory
    const defaultCredential = new DefaultAzureCredential();
    return new BlobServiceClient(serviceBaseURL, defaultCredential);
  } else if (sasToken) {
    const anonymousCredential = new AnonymousCredential();
    return new BlobServiceClient(
      `${serviceBaseURL}${sasToken}`,
      anonymousCredential
    );
  } else if (connStr) {
    return BlobServiceClient.fromConnectionString(connStr);
  } else if (account && accountKey) {
    const sharedKeyCredential = new StorageSharedKeyCredential(
      account,
      accountKey
    );
    return new BlobServiceClient(serviceBaseURL, sharedKeyCredential);
  } else {
    throw new Error(
      "Error in configuration, please be sure to configure the Azure account and accountKey or the connectionString."
    );
  }
};

module.exports = {
  provider: "azure-storage-blob",
  auth: {
    tenant: {
      label: "Azure Tenant Id",
      type: "text",
    },
    client: {
      label: "Azure Client Id",
      type: "text",
    },
    secret: {
      label: "Azure Client Secret",
      type: "text",
    },
    account: {
      label: "Account Name",
      type: "text",
    },
    accountKey: {
      label: "Secret Access Key",
      type: "text",
    },
    connectionString: {
      label: "Connection String",
      type: "text",
    },
    containerName: {
      label: "Container name",
      type: "text",
    },
    sasToken: {
      label: "SAS Token",
      type: "text",
    },
    defaultPath: {
      label: "The path to use when there is none being specified",
      type: "text",
    },
  },

  init: (config: StorageBlobConfig) => {
    const defaultPath = trimParam(config.defaultPath) || "assets";
    const containerName = <string>trimParam(config.containerName);

    const client = <BlobServiceClient>createClient(config);

    return {
      uploadStream(file: StrapiFile, customParams = {}) {
        return new Promise<void>(async (resolve, reject) => {
          try {
            const containerClient = client.getContainerClient(containerName);
            // create blobClient for container
            const blobClient = containerClient.getBlockBlobClient(
              `${defaultPath}/${file.name}`
            );
            // set mimetype as determined from browser with file upload control
            const options = {
              blobHTTPHeaders: { blobContentType: file.mime, ...customParams },
            };

            // convert stream to buffer
            const bufferedStream = await streamToBuffer(file.stream as any);
            // upload file
            await blobClient.uploadData(bufferedStream, options);
            // console.log(result)
            file.url = blobClient.url;
            resolve();
          } catch (error) {
            reject(error);
          }
        });
      },
      upload(file: StrapiFile, customParams = {}) {
        return new Promise<void>(async (resolve, reject) => {
          try {
            const containerClient = client.getContainerClient(containerName);

            // create blobClient for container
            const blobClient = containerClient.getBlockBlobClient(
              `${defaultPath}/${file.name}`
            );

            // set mimetype as determined from browser with file upload control
            const options = {
              blobHTTPHeaders: { blobContentType: file.mime, ...customParams },
            };

            // upload file
            await blobClient.uploadData(file, options);

            file.url = blobClient.url;
            resolve();
          } catch (error) {
            reject(error);
          }
        });
      },
      delete(file: StrapiFile, customParams = {}) {
        return new Promise<void>(async (resolve, reject) => {
          try {
            const containerClient = client.getContainerClient(containerName);

            // create blobClient for container
            const blobClient = containerClient.getBlobClient(
              `${defaultPath}/${file.name}`
            );

            await blobClient.delete();
            file.url = blobClient.url;
            resolve();
          } catch (error) {
            reject(error);
          }
        });
      },
    };
  },
};