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

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

import { createApp } from "../../apps/api/src/app.js";
import { ProjectService } from "../../apps/api/src/projects.js";
import { RegistrationService } from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";

const now = Date.parse("2026-08-02T09:00:00.000Z");
const baseHeaders = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" };
const roots: string[] = [];
const registrations: RegistrationService[] = [];
const projects: ProjectService[] = [];

function writeEvidence(value: unknown) {
  const root = process.env.DADA_EVIDENCE_DIR_PROJECT_TRASH;
  if (!root) return;
  mkdirSync(root, { recursive: true });
  writeFileSync(resolve(root, "response.json"), `${JSON.stringify(value, null, 2)}\n`);
}

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

describe("TDD-WP2-PROJ-003 lifecycle API", () => {
  it("exposes owner-only trash, restore, and irreversible purge routes", async () => {
    const root = mkdtempSync(join(tmpdir(), "dada-wp2-03-api-"));
    roots.push(root);
    const databasePath = join(root, "dada.sqlite3");
    const registration = new RegistrationService({
      challengePepper: Buffer.alloc(32, 0x71), clock: () => now,
      currentPrivacyNoticeVersion: "p0a-registration-notice-v1", databasePath,
      invitePepper: Buffer.alloc(32, 0x72), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0x73),
    });
    registrations.push(registration);
    const projectService = new ProjectService({ clock: () => now, databasePath });
    projects.push(projectService);
    const ownerId = randomUUID();
    registration.database.prepare(`INSERT INTO users (
      user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at
    ) VALUES (?, 'trash@example.invalid', 'user', 'active', 1, ?, ?)`).run(ownerId, randomUUID(), now);
    registration.database.prepare("INSERT INTO user_profiles (user_id, creator_name, social_id) VALUES (?, 'Trash User', '@trash_user')").run(ownerId);
    registration.database.prepare("INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at) VALUES (?, 10, 0, ?)").run(ownerId, now);
    const session = registration.issueAuthenticatedSession(ownerId, "user");
    const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, projects: projectService, registration });
    const cookie = `dada_session=${session.sessionToken}`;
    const sessionResponse = await app.inject({ headers: { ...baseHeaders, cookie }, method: "GET", url: "/api/v1/auth/session" });
    const csrf = sessionResponse.json().csrf_token as string;
    const mutationHeaders = { ...baseHeaders, cookie, "x-csrf-token": csrf };
    const target = projectService.createProjectForGeneration({ ownerId, prompt: "API 回收站", ratio: "3:4", status: "failed" });
    const lifecycle = (action: "trash" | "restore" | "purge") => app.inject({
      headers: mutationHeaders, method: "POST", url: `/api/v1/projects/${target.project.projectId}/${action}`,
    });

    const trashed = await lifecycle("trash");
    expect(trashed.statusCode).toBe(200);
    expect(trashed.json()).toMatchObject({ project_id: target.project.projectId, status: "trashed" });
    const firstTimes = { deleted_at: trashed.json().deleted_at, purge_at: trashed.json().purge_at };
    for (let index = 0; index < 20; index += 1) {
      projectService.createProjectForGeneration({ ownerId, prompt: `API 名额 ${index}`, ratio: "1:1", status: "failed" });
    }
    const blocked = await lifecycle("restore");
    expect(blocked.statusCode).toBe(409);
    expect(blocked.body).toBe("null");
    const trashedList = await app.inject({ headers: { ...baseHeaders, cookie }, method: "GET", url: "/api/v1/projects?status=trashed" });
    expect(trashedList.json().projects[0]).toMatchObject(firstTimes);

    const fillerId = projectService.listProjects(ownerId, "active")[0].projectId;
    projectService.trashProject(ownerId, fillerId);
    const restored = await lifecycle("restore");
    expect(restored.statusCode).toBe(200);
    expect(restored.json()).toMatchObject({ deleted_at: null, purge_at: null, status: "active" });
    await lifecycle("trash");
    const purged = await lifecycle("purge");
    expect(purged.statusCode).toBe(200);
    expect(purged.json()).toEqual({ project_id: target.project.projectId, status: "purged" });
    const hidden = await app.inject({ headers: { ...baseHeaders, cookie }, method: "GET", url: `/api/v1/projects/${target.project.projectId}` });
    const cannotRestore = await lifecycle("restore");
    expect(hidden.statusCode).toBe(404);
    expect(cannotRestore.statusCode).toBe(404);
    writeEvidence({ blocked_restore: blocked.statusCode, first_times: firstTimes, hidden_after_purge: hidden.statusCode, purged: purged.json(), restored: restored.json() });
    await app.close();
  }, 20_000);
});
