import { randomUUID } from "node:crypto";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

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

import { createApp } from "../../apps/api/src/app.js";
import { AssetPreviewGrantService } from "../../apps/api/src/preview-grants.js";
import { RegistrationService } from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
import { createAssetReleaseManifest } from "../../packages/asset-release-manifest/src/index.js";

const start = Date.parse("2026-08-04T08:00:00.000Z");
const releaseVersion = "asset-20260804.1";
const previewResourceId = "8f9b5c62-7488-4c7a-9f0c-3b8f3fc34f92";
const roots: string[] = [];
const registrations: RegistrationService[] = [];

function harness() {
  const root = mkdtempSync(join(tmpdir(), "dada-wp5-07-"));
  roots.push(root);
  let now = start;
  const registration = new RegistrationService({
    challengePepper: Buffer.alloc(32, 0x71),
    clock: () => now,
    currentPrivacyNoticeVersion: "p0a-registration-notice-v1",
    databasePath: join(root, "dada.sqlite3"),
    invitePepper: Buffer.alloc(32, 0x72),
    resend: new MockResendAdapter(),
    sessionPepper: Buffer.alloc(32, 0x73),
  });
  registrations.push(registration);
  const userId = randomUUID();
  registration.database.prepare(`
    INSERT INTO users (
      user_id, normalized_email, role, status, counts_toward_stage_limit,
      registration_id, created_at
    ) VALUES (?, ?, 'user', 'active', 1, ?, ?)
  `).run(userId, `${userId}@example.invalid`, randomUUID(), start);
  registration.database.prepare(`
    INSERT INTO user_profiles (user_id, creator_name, social_id)
    VALUES (?, 'Preview User', '@preview_user')
  `).run(userId);
  registration.database.prepare(`
    INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at)
    VALUES (?, 10, 0, ?)
  `).run(userId, start);
  const adminId = randomUUID();
  registration.database.prepare(`
    INSERT INTO users (
      user_id, normalized_email, role, status, counts_toward_stage_limit,
      registration_id, created_at
    ) VALUES (?, ?, 'super_admin', 'active', 0, ?, ?)
  `).run(adminId, `${adminId}@example.invalid`, randomUUID(), start);
  registration.database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(adminId);
  const assetReleases = createAssetReleaseManifest({
    items: [{
      access_class: "internal_preview_asset",
      content: Buffer.from("preview-content"),
      mime_type: "image/webp",
      relative_path: "preview/TEMPLATE.webp",
      resource_id: previewResourceId,
      root_ref: "canonical-assets",
    }],
    release_version: releaseVersion,
  });
  const service = new AssetPreviewGrantService({ assetReleases, registration, clock: () => now });
  return {
    advance(milliseconds: number) { now += milliseconds; },
    adminId,
    registration,
    service,
    userId,
  };
}

afterEach(() => {
  for (const registration of registrations.splice(0)) registration.close();
  for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
});

describe("TASK-WP5-07 internal preview grant lifecycle", () => {
  it("keeps ordinary role, returns randomized manifest item IDs, and blocks revoked/expired grants", () => {
    const test = harness();
    const batch = test.service.createBatch({
      name: "WP5 preview batch",
      adminUserId: test.adminId,
    });
    test.service.addBatchItems({
      adminUserId: test.adminId,
      batchId: batch.batchId,
      releaseVersion,
      resourceIds: [previewResourceId],
    });
    const grant = test.service.grant({
      adminUserId: test.adminId,
      batchId: batch.batchId,
      expiresAt: start + 60_000,
      userId: test.userId,
    });

    const firstManifest = test.service.projectManifest({ releaseVersion, userId: test.userId });
    expect(firstManifest?.items).toHaveLength(1);
    expect(firstManifest?.items[0].resource_id).not.toBe(previewResourceId);
    expect(firstManifest?.items[0].url).toContain(firstManifest?.items[0].resource_id ?? "");
    expect(test.service.readManifestItem({
      manifestItemId: firstManifest!.items[0].resource_id,
      releaseVersion,
      userId: test.userId,
    })?.bytes).toEqual(Buffer.from("preview-content"));
    expect(test.registration.database.prepare("SELECT role FROM users WHERE user_id = ?").get(test.userId)).toEqual({ role: "user" });

    test.service.revoke({ adminUserId: test.adminId, grantId: grant.grantId });
    expect(test.service.projectManifest({ releaseVersion, userId: test.userId })).toBeUndefined();
    expect(test.service.readManifestItem({
      manifestItemId: firstManifest!.items[0].resource_id,
      releaseVersion,
      userId: test.userId,
    })).toBeUndefined();

    const secondGrant = test.service.grant({
      adminUserId: test.adminId,
      batchId: batch.batchId,
      expiresAt: start + 10_000,
      userId: test.userId,
    });
    expect(secondGrant.status).toBe("active");
    test.advance(10_001);
    expect(test.service.projectManifest({ releaseVersion, userId: test.userId })).toBeUndefined();
    expect(test.registration.database.prepare("SELECT status FROM asset_preview_grants WHERE grant_id = ?").get(secondGrant.grantId)).toEqual({ status: "expired" });

    test.registration.database.prepare("UPDATE users SET status = 'active' WHERE user_id = ?").run(test.userId);
    test.service.grant({
      adminUserId: test.adminId,
      batchId: batch.batchId,
      expiresAt: start + 120_000,
      userId: test.userId,
    });
    test.registration.changeUserStatus(test.userId, "suspended");
    expect(test.service.projectManifest({ releaseVersion, userId: test.userId })).toBeUndefined();
    expect(test.registration.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs WHERE operation_type LIKE 'preview_grant_%'").get()).toEqual({ count: 5 });
  });

  it("serves the randomized item through the authenticated no-store route", async () => {
    const test = harness();
    const batch = test.service.createBatch({ name: "WP5 route batch", adminUserId: test.adminId });
    test.service.addBatchItems({
      adminUserId: test.adminId,
      batchId: batch.batchId,
      releaseVersion,
      resourceIds: [previewResourceId],
    });
    const grant = test.service.grant({ adminUserId: test.adminId, batchId: batch.batchId, expiresAt: start + 60_000, userId: test.userId });
    const session = test.registration.issueAuthenticatedSession(test.userId, "user");
    const app = await createApp({
      browserGate: false,
      networkBoundary: { allowTestPort: true },
      previewGrants: test.service,
      registration: test.registration,
    });
    try {
      const headers = { cookie: `dada_session=${session.sessionToken}`, host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" };
      const manifest = await app.inject({ headers, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/manifest` });
      expect(manifest.statusCode).toBe(200);
      const itemId = manifest.json().items[0].resource_id;
      expect(itemId).not.toBe(previewResourceId);
      const asset = await app.inject({ headers, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/${itemId}` });
      expect(asset.statusCode).toBe(200);
      expect(asset.headers["cache-control"]).toBe("private, no-store");
      expect(asset.rawPayload).toEqual(Buffer.from("preview-content"));
      test.service.revoke({ adminUserId: test.adminId, grantId: grant.grantId });
      const revoked = await app.inject({ headers, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/${itemId}` });
      expect(revoked.statusCode).toBe(404);
    } finally {
      await app.close();
    }
  });
});
