import { createHash } from "node:crypto";

import { describe, expect, it } from "vitest";

import { createAssetReleaseManifest } from "../../packages/asset-release-manifest/src/index.js";

const releaseVersion = "asset-20260803.1";
const publicId = "7f0c9530-a7d9-4bf1-8c65-0e9298dd04ac";
const previewId = "ab18fd72-60e1-44e3-a9a0-3dfccb12e17c";
const privateId = "e3792605-5252-4d3b-a101-827408ab3515";

function fixture() {
  return createAssetReleaseManifest({
    items: [
      {
        access_class: "public_release_asset",
        cache_kind: "thumbnail",
        content: Buffer.from("public-content"),
        mime_type: "image/png",
        relative_path: "public/thumbnails/FLOWER001.png",
        resource_id: publicId,
        root_ref: "canonical-assets",
      },
      {
        access_class: "internal_preview_asset",
        content: Buffer.from("preview-content"),
        mime_type: "image/webp",
        relative_path: "preview/batch-7/FLOWER009.webp",
        resource_id: previewId,
        root_ref: "canonical-assets",
      },
      {
        access_class: "private_user_asset",
        content: Buffer.from("private-content"),
        mime_type: "image/png",
        owner_id: "owner-1",
        relative_path: "private/owner-1/generated.png",
        resource_id: privateId,
        root_ref: "managed-assets",
      },
    ],
    release_version: releaseVersion,
  });
}

describe("TASK-WP5-04 immutable asset release manifest", () => {
  it("projects only the selected access class and never exposes source paths", () => {
    const manifest = fixture();
    const projected = manifest.project("public_release_asset", releaseVersion);

    expect(Object.isFrozen(projected)).toBe(true);
    expect(projected?.manifest_sha256).toMatch(/^[0-9a-f]{64}$/);
    expect(projected?.items).toEqual([expect.objectContaining({
      access_class: "public_release_asset",
      resource_id: publicId,
      sha256: createHash("sha256").update("public-content").digest("hex"),
      url: `/api/v1/assets/public/${releaseVersion}/${publicId}`,
    })]);
    const serialized = JSON.stringify(projected);
    expect(serialized).not.toContain(previewId);
    expect(serialized).not.toContain(privateId);
    expect(serialized).not.toContain("relative_path");
    expect(serialized).not.toContain("root_ref");
    expect(serialized).not.toMatch(/[A-Z]:\\\\/i);
  });

  it("keeps resource identifiers opaque, verifies file hashes, and rejects unsafe manifests", () => {
    const manifest = fixture();
    expect(manifest.read("public_release_asset", releaseVersion, publicId)?.bytes).toEqual(Buffer.from("public-content"));
    expect(manifest.read("public_release_asset", releaseVersion, previewId)).toBeUndefined();
    expect(manifest.project("private_user_asset", releaseVersion, { ownerId: "owner-1" })?.items)
      .toEqual([expect.objectContaining({ resource_id: privateId })]);
    expect(manifest.project("private_user_asset", releaseVersion, { ownerId: "owner-2" })?.items).toEqual([]);

    expect(() => createAssetReleaseManifest({
      items: [{
        access_class: "public_release_asset",
        cache_kind: "thumbnail",
        content: Buffer.from("tampered"),
        mime_type: "image/png",
        relative_path: "public/tampered.png",
        resource_id: publicId,
        root_ref: "canonical-assets",
        sha256: "0".repeat(64),
      }],
      release_version: releaseVersion,
    })).toThrow(/sha-?256/i);
    expect(() => createAssetReleaseManifest({
      items: [{
        access_class: "public_release_asset",
        cache_kind: "thumbnail",
        content: Buffer.from("unsafe"),
        mime_type: "image/png",
        relative_path: "/outside/private.png",
        resource_id: publicId,
        root_ref: "canonical-assets",
      }],
      release_version: releaseVersion,
    })).toThrow(/relative path/i);
  });
});
