import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { BaseService } from "./baseService.server";
import { env } from "~/env.server";
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
import { S3Client } from "@aws-sdk/client-s3";
import { customAlphabet } from "nanoid";
import { errAsync, fromPromise } from "neverthrow";

const nanoid = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 24);
const objectStoreClient =
  env.ARTIFACTS_OBJECT_STORE_ACCESS_KEY_ID &&
  env.ARTIFACTS_OBJECT_STORE_SECRET_ACCESS_KEY &&
  env.ARTIFACTS_OBJECT_STORE_BASE_URL
    ? new S3Client({
        credentials: {
          accessKeyId: env.ARTIFACTS_OBJECT_STORE_ACCESS_KEY_ID,
          secretAccessKey: env.ARTIFACTS_OBJECT_STORE_SECRET_ACCESS_KEY,
        },
        region: env.ARTIFACTS_OBJECT_STORE_REGION,
        endpoint: env.ARTIFACTS_OBJECT_STORE_BASE_URL,
        forcePathStyle: true,
      })
    : new S3Client();

const artifactKeyPrefixByType = {
  deployment_context: "deployments",
} as const;
const artifactBytesSizeLimitByType = {
  deployment_context: 100 * 1024 * 1024, // 100MB
} as const;

export class ArtifactsService extends BaseService {
  private readonly bucket = env.ARTIFACTS_OBJECT_STORE_BUCKET;

  public createArtifact(
    type: "deployment_context",
    authenticatedEnv: AuthenticatedEnvironment,
    contentLength?: number
  ) {
    const limit = artifactBytesSizeLimitByType[type];

    // this is just a validation using client-side data
    // the actual limit will be enforced by S3
    if (contentLength && contentLength > limit) {
      return errAsync({
        type: "artifact_size_exceeds_limit" as const,
        contentLength,
        sizeLimit: limit,
      });
    }

    const uniqueId = nanoid();
    const key = `${artifactKeyPrefixByType[type]}/${authenticatedEnv.project.externalRef}/${authenticatedEnv.slug}/${uniqueId}.tar.gz`;

    return this.createPresignedPost(key, limit, contentLength).map((result) => ({
      artifactKey: key,
      uploadUrl: result.url,
      uploadFields: result.fields,
      expiresAt: result.expiresAt,
    }));
  }

  private createPresignedPost(key: string, sizeLimit: number, contentLength?: number) {
    if (!this.bucket) {
      return errAsync({
        type: "artifacts_bucket_not_configured" as const,
      });
    }

    const ttlSeconds = 300; // 5 minutes
    const expiresAt = new Date(Date.now() + ttlSeconds * 1000);

    return fromPromise(
      createPresignedPost(objectStoreClient, {
        Bucket: this.bucket,
        Key: key,
        Conditions: [["content-length-range", 0, sizeLimit]],
        Fields: {
          "Content-Type": "application/gzip",
        },
        Expires: ttlSeconds,
      }),
      (error) => ({
        type: "failed_to_create_presigned_post" as const,
        cause: error,
      })
    ).map((result) => ({
      ...result,
      expiresAt,
    }));
  }
}
