import { createHmac, randomUUID } from "node:crypto";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import {
  DeleteObjectCommand,
  GetObjectCommand,
  HeadBucketCommand,
  PutObjectCommand,
  S3Client,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

export interface PresignedUpload {
  key: string;
  uploadUrl: string;
  method: "PUT";
  headers: Record<string, string>;
  expiresAt: string;
}

export interface StorageService {
  presignUpload(prefix: string, contentType: string): Promise<PresignedUpload>;
  /** URL som workern/AAMOS kan läsa bilden från. */
  getReadUrl(key: string): Promise<string>;
  putObject(key: string, data: Buffer, contentType: string): Promise<void>;
  getObject(key: string): Promise<Buffer | null>;
  /** GDPR / rättning: radera ett objekt. Får inte kasta om nyckeln saknas. */
  deleteObject(key: string): Promise<void>;
  /**
   * Försök extrahera lagringsnyckeln ur en läs-URL. Returnerar null om URL:en
   * inte känns igen (t.ex. extern bild). Används vid GDPR-radering så att vi
   * kan radera objekt även när endast URL:en är sparad.
   */
  extractKeyFromUrl(url: string): string | null;
}

const MOCK_ROOT = path.resolve(process.cwd(), ".data/s3");

export class MockStorage implements StorageService {
  constructor(
    private readonly baseUrl: string,
    private readonly signingSecret: string,
  ) {}

  private sign(key: string): string {
    return createHmac("sha256", this.signingSecret).update(key).digest("hex").slice(0, 32);
  }

  async presignUpload(prefix: string, contentType: string): Promise<PresignedUpload> {
    const ext = contentType.split("/")[1] ?? "bin";
    const key = `${prefix}/${randomUUID()}.${ext}`;
    const sig = this.sign(key);
    return {
      key,
      uploadUrl: `${this.baseUrl}/v1/mock-s3/${encodeURIComponent(key)}?sig=${sig}`,
      method: "PUT",
      headers: { "content-type": contentType },
      expiresAt: new Date(Date.now() + 15 * 60_000).toISOString(),
    };
  }

  async getReadUrl(key: string): Promise<string> {
    return `${this.baseUrl}/v1/mock-s3/${encodeURIComponent(key)}?sig=${this.sign(key)}`;
  }

  async putObject(key: string, data: Buffer, _contentType?: string): Promise<void> {
    const filePath = path.join(MOCK_ROOT, key);
    await mkdir(path.dirname(filePath), { recursive: true });
    await writeFile(filePath, data);
  }

  async getObject(key: string): Promise<Buffer | null> {
    try {
      return await readFile(path.join(MOCK_ROOT, key));
    } catch {
      return null;
    }
  }

  async deleteObject(key: string): Promise<void> {
    await rm(path.join(MOCK_ROOT, key), { force: true }).catch(() => {});
  }

  extractKeyFromUrl(url: string): string | null {
    try {
      const parsed = new URL(url);
      const prefix = "/v1/mock-s3/";
      if (!parsed.pathname.startsWith(prefix)) return null;
      return decodeURIComponent(parsed.pathname.slice(prefix.length));
    } catch {
      return null;
    }
  }

  verifySignature(key: string, sig: string): boolean {
    return this.sign(key) === sig;
  }
}

export interface AwsStorageConfig {
  bucket: string;
  region: string;
  endpoint?: string;
  accessKeyId?: string;
  secretAccessKey?: string;
}

/** Riktig S3 (AWS eller S3-kompatibel via S3_ENDPOINT). */
export class AwsStorage implements StorageService {
  private readonly client: S3Client;
  private readonly bucket: string;

  constructor(cfg: AwsStorageConfig) {
    this.bucket = cfg.bucket;
    this.client = new S3Client({
      region: cfg.region,
      ...(cfg.endpoint ? { endpoint: cfg.endpoint, forcePathStyle: true } : {}),
      ...(cfg.accessKeyId && cfg.secretAccessKey
        ? { credentials: { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey } }
        : {}), // annars IAM-roll/instansprofil (rekommenderat i AWS)
    });
  }

  /** Snabb verifiering vid uppstart – hellre högljutt fel än trasiga uppladdningar. */
  async healthCheck(): Promise<void> {
    await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));
  }

  async presignUpload(prefix: string, contentType: string): Promise<PresignedUpload> {
    const ext = contentType.split("/")[1] ?? "bin";
    const key = `${prefix}/${randomUUID()}.${ext}`;
    const uploadUrl = await getSignedUrl(
      this.client,
      new PutObjectCommand({ Bucket: this.bucket, Key: key, ContentType: contentType }),
      { expiresIn: 15 * 60 },
    );
    return {
      key,
      uploadUrl,
      method: "PUT",
      headers: { "content-type": contentType },
      expiresAt: new Date(Date.now() + 15 * 60_000).toISOString(),
    };
  }

  async getReadUrl(key: string): Promise<string> {
    return getSignedUrl(this.client, new GetObjectCommand({ Bucket: this.bucket, Key: key }), {
      expiresIn: 60 * 60,
    });
  }

  async putObject(key: string, data: Buffer, contentType: string): Promise<void> {
    await this.client.send(
      new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: data, ContentType: contentType }),
    );
  }

  async getObject(key: string): Promise<Buffer | null> {
    try {
      const res = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: key }));
      const bytes = await res.Body?.transformToByteArray();
      return bytes ? Buffer.from(bytes) : null;
    } catch {
      return null;
    }
  }

  async deleteObject(key: string): Promise<void> {
    try {
      await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));
    } catch (err) {
      const code = (err as { Code?: string }).Code;
      if (code !== "NoSuchKey" && code !== "NotFound") {
        throw err;
      }
    }
  }

  extractKeyFromUrl(url: string): string | null {
    try {
      const parsed = new URL(url);
      if (parsed.hostname.startsWith(`${this.bucket}.`)) {
        return decodeURIComponent(parsed.pathname.replace(/^\//, ""));
      }
      const pathPrefix = `/${this.bucket}/`;
      if (parsed.pathname.startsWith(pathPrefix)) {
        return decodeURIComponent(parsed.pathname.slice(pathPrefix.length));
      }
      return null;
    } catch {
      return null;
    }
  }
}

/**
 * Skapa rätt lagringstjänst utifrån S3_MODE. Används av API och worker.
 */
export function createStorageService(cfg: {
  mode: "aws" | "mock";
  baseUrl: string;
  signingSecret: string;
  s3Bucket: string;
  s3Region: string;
  s3Endpoint?: string;
  s3AccessKeyId?: string;
  s3SecretAccessKey?: string;
}): StorageService {
  if (cfg.mode === "aws") {
    return new AwsStorage({
      bucket: cfg.s3Bucket,
      region: cfg.s3Region,
      endpoint: cfg.s3Endpoint,
      accessKeyId: cfg.s3AccessKeyId,
      secretAccessKey: cfg.s3SecretAccessKey,
    });
  }
  return new MockStorage(cfg.baseUrl, cfg.signingSecret);
}
