import { env } from "cloudflare:test";
import { beforeEach, describe, expect, it } from "vitest";
import app from "../src/app";
import { activateTestSite, authHeaders, seedLicenseAndBucket } from "./helpers";

const SHA = "b".repeat(64);

function signUpload(token: string, body: unknown) {
  return app.request(
    "/v1/storage/blobs/sign-upload",
    { method: "POST", headers: authHeaders(token), body: JSON.stringify(body) },
    env,
  );
}

describe("POST /v1/storage/blobs/sign-upload", () => {
  beforeEach(seedLicenseAndBucket);

  it("requires auth", async () => {
    const res = await app.request(
      "/v1/storage/blobs/sign-upload",
      { method: "POST", body: JSON.stringify({ sha256: SHA, size_bytes: 10 }) },
      env,
    );
    expect(res.status).toBe(401);
  });

  it("returns a presigned PUT URL and records a pending blob + usage log", async () => {
    const { site_id, site_token } = await activateTestSite();
    const res = await signUpload(site_token, { sha256: SHA, size_bytes: 1234 });
    expect(res.status).toBe(200);
    const body = (await res.json()) as {
      status: string;
      upload_url: string;
      expires_at: string;
    };
    expect(body.status).toBe("upload");
    const u = new URL(body.upload_url);
    expect(u.pathname).toBe(`/wpback-test-001/s/${site_id}/blobs/${SHA}`);
    expect(u.searchParams.get("X-Amz-Expires")).toBe("900");

    const blob = await env.DB.prepare(
      "SELECT status, size_bytes FROM blobs WHERE site_id = ? AND sha256 = ?",
    )
      .bind(site_id, SHA)
      .first<{ status: string; size_bytes: number }>();
    expect(blob).toEqual({ status: "pending", size_bytes: 1234 });

    const log = await env.DB.prepare(
      "SELECT op, size_bytes FROM usage_log WHERE site_id = ?",
    )
      .bind(site_id)
      .first<{ op: string; size_bytes: number }>();
    expect(log).toEqual({ op: "sign_upload", size_bytes: 1234 });
  });

  it("returns exists for an already stored blob", async () => {
    const { site_id, site_token } = await activateTestSite();
    await env.DB.prepare(
      "INSERT INTO blobs (site_id, sha256, size_bytes, status, created_at) VALUES (?, ?, 10, 'stored', '2020-01-01T00:00:00Z')",
    )
      .bind(site_id, SHA)
      .run();
    const res = await signUpload(site_token, { sha256: SHA, size_bytes: 10 });
    expect(res.status).toBe(200);
    expect(await res.json()).toEqual({ status: "exists" });

    const touched = await env.DB.prepare(
      "SELECT created_at FROM blobs WHERE site_id = ? AND sha256 = ?",
    )
      .bind(site_id, SHA)
      .first<{ created_at: string }>();
    expect(touched?.created_at).not.toBe("2020-01-01T00:00:00Z");
  });

  it("re-arms created_at when re-signing an existing pending blob", async () => {
    const { site_id, site_token } = await activateTestSite();
    await env.DB.prepare(
      "INSERT INTO blobs (site_id, sha256, size_bytes, status, created_at) VALUES (?, ?, 1234, 'pending', '2020-01-01T00:00:00Z')",
    )
      .bind(site_id, SHA)
      .run();
    const res = await signUpload(site_token, { sha256: SHA, size_bytes: 1234 });
    expect(res.status).toBe(200);
    expect(((await res.json()) as { status: string }).status).toBe("upload");

    const row = await env.DB.prepare(
      "SELECT created_at FROM blobs WHERE site_id = ? AND sha256 = ?",
    )
      .bind(site_id, SHA)
      .first<{ created_at: string }>();
    expect(row?.created_at).not.toBe("2020-01-01T00:00:00Z");
  });

  it("rejects invalid sha256 and sizes with 400", async () => {
    const { site_token } = await activateTestSite();
    expect(
      (await signUpload(site_token, { sha256: "XYZ", size_bytes: 10 })).status,
    ).toBe(400);
    expect(
      (await signUpload(site_token, { sha256: SHA, size_bytes: 0 })).status,
    ).toBe(400);
    expect(
      (await signUpload(site_token, { sha256: SHA, size_bytes: 6_000_000_000 })).status,
    ).toBe(400);
  });

  it("rejects over-quota uploads with 402", async () => {
    const { site_token } = await activateTestSite(); // limit 1_000_000
    const res = await signUpload(site_token, { sha256: SHA, size_bytes: 1_000_001 });
    expect(res.status).toBe(402);
    const body = (await res.json()) as { error: { code: string } };
    expect(body.error.code).toBe("quota_exceeded");
  });

  it("counts pending reservations in the quota check", async () => {
    const { site_token } = await activateTestSite();
    const shaA = "1".repeat(64);
    const shaB = "2".repeat(64);
    // Réservation A : 600k, sous quota
    expect(
      (await signUpload(site_token, { sha256: shaA, size_bytes: 600_000 })).status,
    ).toBe(200);
    // Réservation B : 600k de plus -> dépasse 1M avec le pending A
    const res = await signUpload(site_token, { sha256: shaB, size_bytes: 600_000 });
    expect(res.status).toBe(402);
    // Re-demande de A (même sha) : pas de double comptage, toujours 200
    expect(
      (await signUpload(site_token, { sha256: shaA, size_bytes: 600_000 })).status,
    ).toBe(200);
  });
});
