/**
 * Fullstack Composer Agent Test — SF-05
 */

import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, rmSync, readFileSync } from "node:fs";

const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");

let composeFullstack, loadJSON, writeFullstack;

before(async () => {
  const mod = await import("../scripts/fullstack-composer-agent.mjs");
  composeFullstack = mod.composeFullstack;
  loadJSON = mod.loadJSON;
  writeFullstack = mod.writeFullstack;
});

function loadPRD(name) {
  return JSON.parse(readFileSync(resolve(WORKSPACE, `test/fixtures/architecture-agent/${name}-prd.json`), "utf-8"));
}
function loadArch() {
  return JSON.parse(readFileSync(resolve(WORKSPACE, "architecture-example.json"), "utf-8"));
}

// Demo RequirementPackage
const DEMO_PRD = {
  projectName: "PetCare",
  chineseName: "宠物管理",
  domain: "pet",
  summary: "一款宠物健康管理应用",
  features: [
    { name: "宠物档案", priority: "P0" },
    { name: "健康日程", priority: "P0" },
  ],
  userStories: [{ id: "US-001", as: "宠物主人", want: "记录宠物", priority: "P0" }],
  personas: [{ name: "宠物主人" }],
  pages: [
    { name: "首页", route: "/home" },
    { name: "宠物详情", route: "/pet/:id" },
  ],
};

const DEMO_ARCH = {
  projectName: "PetCare",
  databaseSchema: [
    {
      table: "pets",
      description: "宠物档案",
      fields: [
        { name: "id", type: "TEXT", constraints: "PK" },
        { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" },
        { name: "name", type: "TEXT", constraints: "NOT NULL" },
        { name: "species", type: "TEXT" },
        { name: "created_at", type: "TEXT", constraints: "DEFAULT (datetime('now'))" },
      ],
    },
    {
      table: "schedules",
      description: "健康日程",
      fields: [
        { name: "id", type: "TEXT", constraints: "PK" },
        { name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" },
        { name: "title", type: "TEXT", constraints: "NOT NULL" },
      ],
    },
  ],
  apiDesign: [
    {
      resource: "pets",
      basePath: "/api/pets",
      endpoints: [
        { method: "GET", path: "/api/pets" },
        { method: "POST", path: "/api/pets" },
        { method: "GET", path: "/api/pets/:id" },
        { method: "PUT", path: "/api/pets/:id" },
        { method: "DELETE", path: "/api/pets/:id" },
      ],
    },
    {
      resource: "schedules",
      basePath: "/api/schedules",
      endpoints: [
        { method: "GET", path: "/api/schedules" },
        { method: "POST", path: "/api/schedules" },
      ],
    },
  ],
};

// ═══════════════════════════════════════════════════════
describe("1 — 全栈项目目录结构", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("生成完整项目（无错误）", () => {
    assert.ok(!result.error, `Unexpected error: ${result.error}`);
    assert.ok(result.stats.totalFiles >= 30, `Expected >= 30 files, got ${result.stats.totalFiles}`);
  });

  it("apps/web 存在", () => {
    assert.ok(result.stats.webFiles >= 10, `Expected >= 10 web files, got ${result.stats.webFiles}`);
    assert.ok(result.files["apps/web/package.json"], "Missing web package.json");
    assert.ok(result.files["apps/web/tsconfig.json"], "Missing web tsconfig.json");
  });

  it("apps/api 存在", () => {
    assert.ok(result.stats.apiFiles >= 10, `Expected >= 10 api files, got ${result.stats.apiFiles}`);
    assert.ok(result.files["apps/api/package.json"], "Missing api package.json");
    assert.ok(result.files["apps/api/tsconfig.json"], "Missing api tsconfig.json");
    assert.ok(result.files["apps/api/src/index.ts"], "Missing api entry point");
    assert.ok(result.files["apps/api/src/routes/auth.ts"], "Missing auth routes");
  });

  it("packages/shared-types 存在", () => {
    assert.ok(result.files["packages/shared-types/package.json"], "Missing shared-types package.json");
    assert.ok(result.files["packages/shared-types/src/index.ts"], "Missing shared-types index");
    assert.ok(result.files["packages/shared-types/tsconfig.json"], "Missing shared-types tsconfig");
  });

  it("packages/shared-config 存在", () => {
    assert.ok(result.files["packages/shared-config/package.json"], "Missing shared-config package.json");
    assert.ok(result.files["packages/shared-config/src/index.ts"], "Missing shared-config index");
  });
});

// ═══════════════════════════════════════════════════════
describe("2 — 根目录文件", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("根 package.json 是 workspace 配置", () => {
    const pkg = JSON.parse(result.files["package.json"]);
    assert.ok(pkg.workspaces, "Missing workspaces field");
    assert.ok(pkg.workspaces.includes("apps/*"), "Missing apps/* workspace");
    assert.ok(pkg.workspaces.includes("packages/*"), "Missing packages/* workspace");
    assert.ok(pkg.scripts.dev, "Missing dev script");
    assert.ok(pkg.scripts.build, "Missing build script");
    assert.ok(pkg.scripts["dev:web"], "Missing dev:web script");
    assert.ok(pkg.scripts["dev:api"], "Missing dev:api script");
  });

  it("根 README.md 存在", () => {
    assert.ok(result.files["README.md"]);
    const readme = result.files["README.md"];
    assert.ok(readme.includes("Quick Start"), "Missing Quick Start section");
    assert.ok(readme.includes("npm install"), "Missing install instructions");
    assert.ok(readme.includes("npm run dev"), "Missing dev instructions");
    assert.ok(readme.includes("web"), "Missing web reference");
    assert.ok(readme.includes("api"), "Missing api reference");
    assert.ok(readme.includes("Architecture"), "Missing Architecture section");
  });

  it(".env.example 存在", () => {
    assert.ok(result.files[".env.example"]);
    const env = result.files[".env.example"];
    assert.ok(env.includes("JWT_SECRET"));
    assert.ok(env.includes("DATABASE_URL"));
    assert.ok(env.includes("NEXT_PUBLIC_API_URL"));
    assert.ok(env.includes("API_PORT"));
  });

  it(".gitignore 存在", () => {
    assert.ok(result.files[".gitignore"]);
    assert.ok(result.files[".gitignore"].includes("node_modules"));
  });
});

// ═══════════════════════════════════════════════════════
describe("3 — scripts/", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("dev.mjs 存在", () => {
    assert.ok(result.files["scripts/dev.mjs"]);
    const dev = result.files["scripts/dev.mjs"];
    assert.ok(dev.includes("spawn"), "Missing spawn call");
    assert.ok(dev.includes("apps/api"), "Missing api reference");
    assert.ok(dev.includes("apps/web"), "Missing web reference");
    assert.ok(dev.includes("SIGINT"), "Missing signal handling");
  });

  it("build.mjs 存在", () => {
    assert.ok(result.files["scripts/build.mjs"]);
    const build = result.files["scripts/build.mjs"];
    assert.ok(build.includes("apps/api"), "Missing api reference");
    assert.ok(build.includes("apps/web"), "Missing web reference");
    assert.ok(build.includes("execSync"), "Missing execSync call");
  });
});

// ═══════════════════════════════════════════════════════
describe("4 — shared-types 包", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("包含实体类型", () => {
    const types = result.files["packages/shared-types/src/index.ts"];
    assert.ok(types.includes("interface User"), "Missing User type");
    assert.ok(types.includes("interface Pet"), "Missing Pet type");
    assert.ok(types.includes("interface Schedule"), "Missing Schedule type");
  });

  it("包含 API 响应包装类型", () => {
    const types = result.files["packages/shared-types/src/index.ts"];
    assert.ok(types.includes("ApiResponse"), "Missing ApiResponse");
    assert.ok(types.includes("PaginatedResponse"), "Missing PaginatedResponse");
    assert.ok(types.includes("ErrorResponse"), "Missing ErrorResponse");
  });

  it("package.json 正确命名", () => {
    const pkg = JSON.parse(result.files["packages/shared-types/package.json"]);
    assert.equal(pkg.name, "@shared/types");
  });
});

// ═══════════════════════════════════════════════════════
describe("5 — shared-config 包", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("包含 API 配置", () => {
    const config = result.files["packages/shared-config/src/index.ts"];
    assert.ok(config.includes("API_BASE_URL"), "Missing API_BASE_URL");
    assert.ok(config.includes("API_PREFIX"), "Missing API_PREFIX");
    assert.ok(config.includes("AUTH_TOKEN_KEY"), "Missing AUTH_TOKEN_KEY");
  });

  it("package.json 正确命名", () => {
    const pkg = JSON.parse(result.files["packages/shared-config/package.json"]);
    assert.equal(pkg.name, "@shared/config");
  });
});

// ═══════════════════════════════════════════════════════
describe("6 — 前端适配（monorepo 后处理）", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("前端 package.json 依赖 @shared/types 和 @shared/config", () => {
    const pkg = JSON.parse(result.files["apps/web/package.json"]);
    assert.ok(pkg.dependencies["@shared/types"], "Missing @shared/types dep");
    assert.ok(pkg.dependencies["@shared/config"], "Missing @shared/config dep");
  });

  it("前端 API 服务使用 @shared/config", () => {
    const apiFile = result.files["apps/web/src/services/api.ts"];
    assert.ok(apiFile, "apps/web/src/services/api.ts not found in generated files");
    assert.ok(apiFile.includes("@shared/config"), "Missing @shared/config import");
    assert.ok(apiFile.includes("API_BASE_URL"), "Missing API_BASE_URL usage");
    assert.ok(apiFile.includes("API_PREFIX"), "Missing API_PREFIX usage");
  });

  it("前端类型文件 re-exports shared types", () => {
    const types = result.files["apps/web/src/types/index.ts"];
    assert.ok(types.includes("@shared/types"), "Missing @shared/types import");
  });
});

// ═══════════════════════════════════════════════════════
describe("7 — 后端适配（monorepo 后处理）", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("后端 package.json 依赖 @shared/types 和 @shared/config", () => {
    const pkg = JSON.parse(result.files["apps/api/package.json"]);
    assert.ok(pkg.dependencies["@shared/types"], "Missing @shared/types dep");
    assert.ok(pkg.dependencies["@shared/config"], "Missing @shared/config dep");
  });

  it("后端类型文件 re-exports shared types", () => {
    const types = result.files["apps/api/src/types/index.ts"];
    assert.ok(types.includes("@shared/types"), "Missing @shared/types import");
  });
});

// ═══════════════════════════════════════════════════════
describe("8 — 错误处理", () => {
  it("invalid PRD 返回错误", async () => {
    const result = await composeFullstack({ error: "test" }, {});
    assert.equal(result.error, "INVALID_PRD");
  });

  it("invalid Architecture 返回错误", async () => {
    const result = await composeFullstack(DEMO_PRD, { error: "test" });
    assert.equal(result.error, "INVALID_ARCH");
  });

  it("null PRD 返回错误", async () => {
    const result = await composeFullstack(null, {});
    assert.equal(result.error, "INVALID_PRD");
  });

  it("空的 databaseSchema 仍然生成基础项目", async () => {
    const result = await composeFullstack(
      { projectName: "Empty", summary: "test", features: [], pages: [] },
      { databaseSchema: [], apiDesign: [] }
    );
    assert.ok(!result.error);
    assert.ok(result.files["apps/web/package.json"]);
    assert.ok(result.files["apps/api/package.json"]);
    assert.ok(result.files["packages/shared-types/package.json"]);
  });
});

// ═══════════════════════════════════════════════════════
describe("9 — 文件写入 I/O", () => {
  it("writeFullstack 写入完整目录树", async () => {
    const result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
    const tmpDir = resolve(WORKSPACE, ".tmp-fullstack-test");

    writeFullstack(result, tmpDir);

    // Root
    assert.ok(existsSync(resolve(tmpDir, "package.json")));
    assert.ok(existsSync(resolve(tmpDir, "README.md")));
    assert.ok(existsSync(resolve(tmpDir, ".env.example")));
    assert.ok(existsSync(resolve(tmpDir, "scripts/dev.mjs")));
    assert.ok(existsSync(resolve(tmpDir, "scripts/build.mjs")));

    // apps/web
    assert.ok(existsSync(resolve(tmpDir, "apps/web/package.json")));
    assert.ok(existsSync(resolve(tmpDir, "apps/web/src/app/layout.tsx")));
    assert.ok(existsSync(resolve(tmpDir, "apps/web/src/types/index.ts")));

    // apps/api
    assert.ok(existsSync(resolve(tmpDir, "apps/api/package.json")));
    assert.ok(existsSync(resolve(tmpDir, "apps/api/src/index.ts")));
    assert.ok(existsSync(resolve(tmpDir, "apps/api/src/routes/auth.ts")));
    assert.ok(existsSync(resolve(tmpDir, "apps/api/src/db/schema.ts")));

    // packages
    assert.ok(existsSync(resolve(tmpDir, "packages/shared-types/package.json")));
    assert.ok(existsSync(resolve(tmpDir, "packages/shared-types/src/index.ts")));
    assert.ok(existsSync(resolve(tmpDir, "packages/shared-config/package.json")));
    assert.ok(existsSync(resolve(tmpDir, "packages/shared-config/src/index.ts")));

    rmSync(tmpDir, { recursive: true, force: true });
  });

  it("loadJSON 读取文件", () => {
    const { data } = loadJSON(resolve(WORKSPACE, "prd-example.json"));
    assert.ok(data.projectName);
  });

  it("loadJSON 不存在的文件返回 error", () => {
    const { data, error } = loadJSON("/nonexistent.json");
    assert.equal(data, null);
    assert.ok(error);
  });
});

// ═══════════════════════════════════════════════════════
describe("10 — npm install & build", () => {
  let tmpDir;

  before(async () => {
    tmpDir = resolve(WORKSPACE, ".tmp-fullstack-build");
    rmSync(tmpDir, { recursive: true, force: true });

    const result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
    writeFullstack(result, tmpDir);
  });

  after(() => {
    rmSync(tmpDir, { recursive: true, force: true });
  });

  it("npm install（workspace root 安装，自动链接）", async () => {
    const { execSync } = await import("node:child_process");
    try {
      // Root-level install sets up workspace symlinks for @shared/types, @shared/config
      execSync("npm install --no-audit --no-fund", {
        cwd: tmpDir,
        encoding: "utf-8",
        timeout: 300_000,
        stdio: "pipe",
      });
      // Verify workspace links exist (hoisted to root node_modules)
      const linked = existsSync(resolve(tmpDir, "node_modules/@shared/types")) ||
                     existsSync(resolve(tmpDir, "apps/api/node_modules/@shared/types"));
      assert.ok(linked, "@shared/types not linked");
    } catch (e) {
      assert.fail(`npm install failed: ${(e.stderr || e.message)?.slice(0, 300)}`);
    }
  });

  it("api build 通过（tsc --noEmit）", async () => {
    const { execSync } = await import("node:child_process");
    try {
      execSync("npx tsc --noEmit", {
        cwd: resolve(tmpDir, "apps/api"),
        encoding: "utf-8",
        timeout: 60_000,
        stdio: "pipe",
      });
    } catch (e) {
      assert.fail(`api build failed: ${(e.stdout || e.stderr)?.slice(0, 500)}`);
    }
  });

  it("web 结构有效（目录文件齐全）", () => {
    assert.ok(existsSync(resolve(tmpDir, "apps/web/package.json")));
    assert.ok(existsSync(resolve(tmpDir, "apps/web/src/app/layout.tsx")));
    assert.ok(existsSync(resolve(tmpDir, "apps/web/src/types/index.ts")));
  });
});

// ═══════════════════════════════════════════════════════
describe("11 — 生成的文件非空且有意义", () => {
  let result;
  before(async () => {
    result = await composeFullstack(DEMO_PRD, DEMO_ARCH);
  });

  it("所有文件内容非空（>30 字符）", () => {
    for (const [path, content] of Object.entries(result.files)) {
      assert.ok(content.length > 30, `${path}: too short (${content.length} chars)`);
    }
  });

  it("所有 package.json 可解析", () => {
    const pkgFiles = Object.keys(result.files).filter(f => f.endsWith("package.json"));
    for (const pf of pkgFiles) {
      const pkg = JSON.parse(result.files[pf]);
      assert.ok(pkg.name, `${pf}: missing name`);
    }
  });
});

// ═══════════════════════════════════════════════════════
describe("12 — 多领域覆盖", () => {
  const domains = ["ecommerce", "enterprise", "education", "note"];

  for (const domain of domains) {
    it(`${domain} 域生成成功`, async () => {
      const prd = loadPRD(domain);
      const result = await composeFullstack(prd, loadArch());

      assert.ok(!result.error, `${domain}: ${result.message}`);
      assert.ok(result.stats.webFiles >= 10, `${domain}: web files too few`);
      assert.ok(result.stats.apiFiles >= 10, `${domain}: api files too few`);
      assert.ok(result.files["packages/shared-types/src/index.ts"], `${domain}: shared-types missing`);
    });
  }
});
