import "server-only";

import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";

type ObjectStorageConfig = {
  accessKeyId: string;
  bucketName: string;
  cdnDomain: string | null;
  endpoint: string;
  region: string;
  secretAccessKey: string;
};

let objectStorageClient: S3Client | null = null;

function readRequiredEnv(name: string) {
  const value = process.env[name]?.trim();

  if (!value) {
    throw new Error(`${name}-missing`);
  }

  return value;
}

function readOptionalEnv(name: string) {
  const value = process.env[name]?.trim();
  return value ? value : null;
}

function getObjectStorageConfig(): ObjectStorageConfig {
  return {
    accessKeyId: readRequiredEnv("NCP_ACCESS_KEY"),
    bucketName: readRequiredEnv("NCP_BUCKET_NAME"),
    cdnDomain: readOptionalEnv("CDN_DOMAIN"),
    endpoint: readRequiredEnv("NCP_ENDPOINT"),
    region: readOptionalEnv("NCP_REGION") ?? "kr-standard",
    secretAccessKey: readRequiredEnv("NCP_SECRET_KEY"),
  };
}

function getObjectStorageClient() {
  if (!objectStorageClient) {
    const config = getObjectStorageConfig();

    objectStorageClient = new S3Client({
      credentials: {
        accessKeyId: config.accessKeyId,
        secretAccessKey: config.secretAccessKey,
      },
      endpoint: config.endpoint,
      forcePathStyle: true,
      region: config.region,
    });
  }

  return objectStorageClient;
}

function encodeStorageKey(key: string) {
  return key
    .split("/")
    .map((segment) => encodeURIComponent(segment))
    .join("/");
}

export function buildObjectStoragePublicUrl(key: string) {
  const config = getObjectStorageConfig();
  const encodedKey = encodeStorageKey(key);

  if (config.cdnDomain) {
    return `${config.cdnDomain.replace(/\/$/, "")}/${encodedKey}`;
  }

  return `${config.endpoint.replace(/\/$/, "")}/${encodeURIComponent(config.bucketName)}/${encodedKey}`;
}

export async function uploadBufferToObjectStorage(input: {
  body: Buffer;
  cacheControl?: string;
  contentType: string;
  key: string;
}) {
  const config = getObjectStorageConfig();
  const client = getObjectStorageClient();

  await client.send(
    new PutObjectCommand({
      ACL: "public-read",
      Body: input.body,
      Bucket: config.bucketName,
      CacheControl: input.cacheControl,
      ContentType: input.contentType,
      Key: input.key,
    }),
  );

  return {
    bucketName: config.bucketName,
    key: input.key,
    publicUrl: buildObjectStoragePublicUrl(input.key),
  };
}

export async function readObjectFromObjectStorage(input: { key: string }) {
  const config = getObjectStorageConfig();
  const client = getObjectStorageClient();
  const response = await client.send(
    new GetObjectCommand({
      Bucket: config.bucketName,
      Key: input.key,
    }),
  );

  if (!response.Body) {
    return null;
  }

  const bytes = await response.Body.transformToByteArray();

  return {
    body: Buffer.from(bytes),
    contentType: response.ContentType?.trim() || null,
  };
}

export async function deleteObjectFromObjectStorage(input: { key: string }) {
  const config = getObjectStorageConfig();
  const client = getObjectStorageClient();

  await client.send(
    new DeleteObjectCommand({
      Bucket: config.bucketName,
      Key: input.key,
    }),
  );
}
