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

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

import { ProjectError, ProjectService } from "../../apps/api/src/projects.js";

const baseNow = Date.parse("2026-07-28T08:00:00.000Z");
const roots: string[] = [];
const services: ProjectService[] = [];

function harness() {
  const root = mkdtempSync(join(tmpdir(), "dada-wp2-01-projects-"));
  roots.push(root);
  const service = new ProjectService({ clock: () => baseNow, databasePath: join(root, "dada.sqlite3") });
  services.push(service);
  return service;
}

function writeEvidence(caseId: string, file: string, value: unknown) {
  const root = process.env.DADA_EVIDENCE_DIR_PROJECTS;
  if (!root) return;
  const directory = resolve(root, caseId);
  mkdirSync(directory, { recursive: true });
  writeFileSync(resolve(directory, file), `${JSON.stringify(value, null, 2)}\n`);
}

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

describe("TDD-WP2-PROJ-001-new-versus-continue", () => {
  it("creates only on explicit new work, fixes ratio, and isolates the history limit", () => {
    const service = harness();
    const ownerId = randomUUID();
    for (let index = 0; index < 18; index += 1) {
      service.createProjectForGeneration({ ownerId, prompt: `fixture ${index}`, ratio: "1:1", status: "failed" });
    }

    const first = service.createProjectForGeneration({
      ownerId,
      prompt: "  夜晚城市   中一间明亮的工作室，还有安静的雨声  ",
      ratio: "3:4",
      status: "failed",
    });
    const second = service.createProjectForGeneration({ ownerId, prompt: "第二张作品", ratio: "9:16", status: "failed" });
    expect(first.project.projectId).not.toBe(second.project.projectId);
    expect(first.project.name).toBe("夜晚城市 中一间明亮的工作室，还有安静的雨声 2026-07-28");
    expect(service.listProjects(ownerId, "active")).toHaveLength(20);
    expect(() => service.createProjectForGeneration({ ownerId, prompt: "第 21 个", ratio: "1:1", status: "failed" }))
      .toThrowError(expect.objectContaining<ProjectError>({ code: "project_active_limit" }));

    const beforeContinue = service.listProjects(ownerId, "active").length;
    const continued = service.continueProjectGeneration({
      ownerId, projectId: first.project.projectId, prompt: "同项目继续", ratio: "3:4", status: "running",
    });
    expect(continued.projectId).toBe(first.project.projectId);
    expect(service.listProjects(ownerId, "active")).toHaveLength(beforeContinue);
    expect(() => service.continueProjectGeneration({
      ownerId, projectId: first.project.projectId, prompt: "不能改比例", ratio: "1:1", status: "failed",
    })).toThrowError(expect.objectContaining<ProjectError>({ code: "project_ratio_fixed" }));

    for (let index = 0; index < 10; index += 1) {
      const generation = index === 0
        ? continued
        : service.continueProjectGeneration({
            ownerId, projectId: first.project.projectId, prompt: `历史 ${index}`, ratio: "3:4", status: "running",
          });
      service.recordSuccessfulImage({ generationId: generation.generationId, imageId: randomUUID() });
    }
    expect(service.getProject(ownerId, first.project.projectId).successfulImageCount).toBe(10);
    expect(() => service.continueProjectGeneration({
      ownerId, projectId: first.project.projectId, prompt: "第十一张", ratio: "3:4", status: "queued",
    })).toThrowError(expect.objectContaining<ProjectError>({ code: "project_history_limit" }));

    writeEvidence("TDD-WP2-PROJ-001-new-versus-continue", "db-diff.json", {
      active_projects: 20,
      first_project_images: 10,
      project_delta_on_continue: 0,
    });
    writeEvidence("TDD-WP2-PROJ-001-new-versus-continue", "response.json", {
      history_limit: "blocked",
      new_project_ids: [first.project.projectId, second.project.projectId],
      ratio_change: "blocked",
    });
  });
});

describe("TDD-WP2-PROJ-005-failed-draft-retry", () => {
  it("keeps retries in one draft and trashes only failed-empty selections", () => {
    const service = harness();
    const ownerId = randomUUID();
    const draft = service.createProjectForGeneration({ ownerId, prompt: "失败草稿", ratio: "4:3", status: "failed" });
    const retryOne = service.retryFailedDraft({ ownerId, projectId: draft.project.projectId, prompt: "失败草稿 第一次重试" });
    service.markGenerationFailed(retryOne.generationId, "upstream_timeout");
    const retryTwo = service.retryFailedDraft({ ownerId, projectId: draft.project.projectId, prompt: "失败草稿 第二次重试" });
    service.markGenerationFailed(retryTwo.generationId, "upstream_failed");
    expect(service.getProject(ownerId, draft.project.projectId).generations).toHaveLength(3);

    const otherDraft = service.createProjectForGeneration({ ownerId, prompt: "另一个失败草稿", ratio: "1:1", status: "failed" });
    const successful = service.createProjectForGeneration({ ownerId, prompt: "已有成功图", ratio: "1:1", status: "running" });
    service.recordSuccessfulImage({ generationId: successful.generation.generationId, imageId: randomUUID() });
    const result = service.trashFailedEmpty(ownerId, [draft.project.projectId, otherDraft.project.projectId, successful.project.projectId]);
    expect(result).toEqual({ ignoredProjectIds: [successful.project.projectId], trashedProjectIds: [draft.project.projectId, otherDraft.project.projectId] });
    expect(service.getProject(ownerId, successful.project.projectId).status).toBe("active");
    expect(service.getProject(ownerId, draft.project.projectId).status).toBe("trashed");

    const renamed = service.renameProject(ownerId, successful.project.projectId, "  夏日海报  ");
    expect(renamed.name).toBe("夏日海报");
    writeEvidence("TDD-WP2-PROJ-005-failed-draft-retry", "db-diff.json", {
      failed_empty_trashed: result.trashedProjectIds.length,
      successful_project_status: "active",
      retry_job_count: 3,
    });
    writeEvidence("TDD-WP2-PROJ-005-failed-draft-retry", "response.json", result);
  });
});
