import {
  DeleteObjectCommand,
  PutObjectCommand,
  S3Client,
} from "@aws-sdk/client-s3";
import path from "path";
import {
  R2_ACCESS_KEY_ID,
  R2_ACCOUNT_ENDPOINT,
  R2_BUCKET_NAME,
  R2_PUBLIC_BASE_URL,
  R2_REGION,
  R2_SECRET_ACCESS_KEY,
} from "../config/environment";

export type R2Folder =
  "chat" | "stations" | "trips" | "profiles" | "migrated" | string;

function requireR2Config(): {
  endpoint: string;
  bucket: string;
  publicBase: string;
  accessKeyId: string;
  secretAccessKey: string;
  region: string;
} {
  const endpoint = (R2_ACCOUNT_ENDPOINT ?? "").replace(/\/+$/, "");
  const bucket = (R2_BUCKET_NAME ?? "").trim();
  const publicBase = (R2_PUBLIC_BASE_URL ?? "").replace(/\/+$/, "");
  const accessKeyId = (R2_ACCESS_KEY_ID ?? "").trim();
  const secretAccessKey = (R2_SECRET_ACCESS_KEY ?? "").trim();
  const region = (R2_REGION ?? "auto").trim() || "auto";

  if (!endpoint || !bucket || !publicBase || !accessKeyId || !secretAccessKey) {
    throw new Error(
      "R2 is not configured. Set R2_ACCOUNT_ENDPOINT, R2_BUCKET_NAME, R2_PUBLIC_BASE_URL, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY",
    );
  }

  return { endpoint, bucket, publicBase, accessKeyId, secretAccessKey, region };
}

let client: S3Client | null = null;

function getClient(): S3Client {
  if (client) return client;
  const cfg = requireR2Config();
  client = new S3Client({
    region: cfg.region,
    endpoint: cfg.endpoint,
    credentials: {
      accessKeyId: cfg.accessKeyId,
      secretAccessKey: cfg.secretAccessKey,
    },
    forcePathStyle: true,
  });
  return client;
}

function sanitizeFileName(name: string): string {
  const base = path.basename(name || "file").replace(/\s+/g, "-");
  return base.replace(/[^a-zA-Z0-9._-]/g, "_") || "file";
}

export function buildR2PublicUrl(key: string): string {
  const { publicBase } = requireR2Config();
  const cleanKey = key.replace(/^\/+/, "");
  return `${publicBase}/${cleanKey}`;
}

/** Extract object key from a full R2 public URL, or return path-like input as key. */
export function keyFromR2UrlOrKey(urlOrKey: string): string | null {
  const trimmed = (urlOrKey ?? "").trim();
  if (!trimmed) return null;
  if (/^https?:\/\//i.test(trimmed)) {
    try {
      const { publicBase } = requireR2Config();
      if (trimmed.startsWith(`${publicBase}/`)) {
        return trimmed.slice(publicBase.length + 1);
      }
      const u = new URL(trimmed);
      return u.pathname.replace(/^\/+/, "") || null;
    } catch {
      return null;
    }
  }
  return trimmed.replace(/^\/+/, "");
}

export async function uploadBufferToR2(opts: {
  buffer: Buffer;
  contentType: string;
  folder: R2Folder;
  filename?: string;
  /** Override full object key (skips folder/timestamp naming). */
  key?: string;
}): Promise<{ key: string; url: string }> {
  const { bucket } = requireR2Config();
  const safeName = sanitizeFileName(opts.filename ?? "file");
  const key =
    opts.key ?? `${opts.folder.replace(/\/+$/, "")}/${Date.now()}-${safeName}`;

  await getClient().send(
    new PutObjectCommand({
      Bucket: bucket,
      Key: key,
      Body: opts.buffer,
      ContentType: opts.contentType || "application/octet-stream",
    }),
  );

  return { key, url: buildR2PublicUrl(key) };
}

export async function uploadMulterFileToR2(
  file: Express.Multer.File,
  folder: R2Folder,
): Promise<{ key: string; url: string }> {
  if (!file?.buffer?.length) {
    throw new Error("Upload failed: empty file buffer (use memory storage)");
  }
  return uploadBufferToR2({
    buffer: file.buffer,
    contentType: file.mimetype,
    folder,
    filename: file.originalname,
  });
}

export async function deleteFromR2(keyOrUrl: string): Promise<void> {
  const key = keyFromR2UrlOrKey(keyOrUrl);
  if (!key) return;
  const { bucket } = requireR2Config();
  await getClient().send(
    new DeleteObjectCommand({
      Bucket: bucket,
      Key: key,
    }),
  );
}

/** Backward-compatible alias used by older Storage util callers. */
export async function uploadToS3(
  buffer: Buffer,
  mimetype: string,
  name: string,
  folder: R2Folder = "migrated",
): Promise<string> {
  const { url } = await uploadBufferToR2({
    buffer,
    contentType: mimetype,
    folder,
    filename: name,
  });
  return url;
}
