/**
 * Backend Builder Agent Test — SF-04
 */

import { describe, it, before } 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 buildBackend, loadJSON, writeBackend;

before(async () => {
  const mod = await import("../scripts/backend-builder-agent.mjs");
  buildBackend = mod.buildBackend;
  loadJSON = mod.loadJSON;
  writeBackend = mod.writeBackend;
});

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 that exercises all generators
const DEMO_PRD = {
  projectName: "PetCare",
  chineseName: "宠物管理",
  domain: "pet",
  summary: "一款宠物健康管理应用",
  features: [
    { name: "宠物档案", priority: "P0" },
    { name: "健康日程", priority: "P0" },
    { name: "日常记录", priority: "P1" },
    { name: "附近医院", priority: "P1" },
  ],
  userStories: [
    { id: "US-001", as: "宠物主人", want: "记录宠物信息", soThat: "管理宠物档案", priority: "P0" },
  ],
  personas: [{ name: "宠物主人" }],
};

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: "breed", type: "TEXT" },
        { name: "birth_date", type: "TEXT" },
        { name: "weight_kg", type: "REAL" },
        { name: "avatar_url", type: "TEXT" },
        { name: "created_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
        { name: "updated_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
      ],
      indexes: ["CREATE INDEX IF NOT EXISTS idx_pets_owner ON pets(owner_id)"],
    },
    {
      table: "schedules",
      description: "健康日程表",
      fields: [
        { name: "id", type: "TEXT", constraints: "PK" },
        { name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" },
        { name: "type", type: "TEXT", constraints: "NOT NULL" },
        { name: "title", type: "TEXT", constraints: "NOT NULL" },
        { name: "scheduled_at", type: "TEXT", constraints: "NOT NULL" },
        { name: "completed", type: "INTEGER", constraints: "DEFAULT 0" },
        { name: "created_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
      ],
    },
    {
      table: "daily_logs",
      description: "日常记录表",
      fields: [
        { name: "id", type: "TEXT", constraints: "PK" },
        { name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" },
        { name: "category", type: "TEXT", constraints: "NOT NULL" },
        { name: "value", type: "TEXT", constraints: "NOT NULL" },
        { name: "logged_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
      ],
    },
  ],
  apiDesign: [
    {
      resource: "pets",
      basePath: "/api/pets",
      endpoints: [
        { method: "GET", path: "/api/pets", description: "获取宠物列表" },
        { method: "GET", path: "/api/pets/:id", description: "获取宠物详情" },
        { method: "POST", path: "/api/pets", description: "创建宠物" },
        { method: "PUT", path: "/api/pets/:id", description: "更新宠物" },
        { method: "DELETE", path: "/api/pets/:id", description: "删除宠物" },
      ],
    },
    {
      resource: "schedules",
      basePath: "/api/schedules",
      endpoints: [
        { method: "GET", path: "/api/schedules", description: "获取日程列表" },
        { method: "POST", path: "/api/schedules", description: "创建日程" },
      ],
    },
    {
      resource: "daily-logs",
      basePath: "/api/daily-logs",
      endpoints: [
        { method: "POST", path: "/api/daily-logs", description: "记录日常" },
        { method: "GET", path: "/api/daily-logs", description: "查询日常记录" },
      ],
    },
  ],
};

// ═══════════════════════════════════════════════════════
describe("1 — 目录结构完整性", () => {
  it("生成完整后端项目目录", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(!result.error, `Unexpected error: ${result.error}`);
    assert.ok(result.stats.totalFiles >= 15, `Expected >= 15 files, got ${result.stats.totalFiles}`);
  });

  it("生成 package.json", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["package.json"], "Missing package.json");
    const pkg = JSON.parse(result.files["package.json"]);
    assert.ok(pkg.dependencies.fastify, "fastify dependency missing");
    assert.ok(pkg.dependencies["sql.js"], "sql.js dependency missing");
    assert.ok(pkg.dependencies["@fastify/jwt"], "@fastify/jwt dependency missing");
    assert.ok(pkg.dependencies.bcrypt, "bcrypt dependency missing");
    assert.ok(pkg.devDependencies.typescript, "typescript devDependency missing");
    assert.ok(pkg.scripts.build, "build script missing");
    assert.ok(pkg.scripts.test, "test script missing");
    assert.ok(pkg.scripts.dev, "dev script missing");
  });

  it("生成 tsconfig.json", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["tsconfig.json"], "Missing tsconfig.json");
    const cfg = JSON.parse(result.files["tsconfig.json"]);
    assert.equal(cfg.compilerOptions.strict, true);
    assert.equal(cfg.compilerOptions.module, "ESNext");
    assert.ok(cfg.compilerOptions.outDir);
  });

  it("生成 .env.example", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    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("PORT"));
  });

  it("生成 README.md", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["README.md"]);
    const readme = result.files["README.md"];
    assert.ok(readme.includes("Fastify"));
    assert.ok(readme.includes("SQLite"));
    assert.ok(readme.includes("npm install"));
    assert.ok(readme.includes("## API Endpoints"));
  });

  it("生成 .gitignore", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files[".gitignore"]);
    assert.ok(result.files[".gitignore"].includes("node_modules"));
  });
});

// ═══════════════════════════════════════════════════════
describe("2 — src/index.ts（服务入口）", () => {
  it("生成 src/index.ts", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["src/index.ts"]);
    const content = result.files["src/index.ts"];
    assert.ok(content.includes("Fastify"), "Missing Fastify import");
    assert.ok(content.includes("buildApp"), "Missing buildApp function");
    assert.ok(content.includes("@fastify/jwt"), "Missing JWT plugin");
    assert.ok(content.includes("@fastify/cors"), "Missing CORS plugin");
    assert.ok(content.includes("authRoutes"), "Missing auth routes");
    assert.ok(content.includes("petsRoutes") || content.includes("/api/pets"), "Missing pets routes");
    assert.ok(content.includes("/api/health"), "Missing health check");
  });
});

// ═══════════════════════════════════════════════════════
describe("3 — src/db/（数据库层）", () => {
  it("生成 src/db/schema.ts", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["src/db/schema.ts"]);
    const content = result.files["src/db/schema.ts"];
    assert.ok(content.includes("CREATE TABLE"), "Missing CREATE TABLE");
    assert.ok(content.includes("users"), "Missing users table");
    assert.ok(content.includes("pets"), "Missing pets table");
    assert.ok(content.includes("schedules"), "Missing schedules table");
    assert.ok(content.includes("daily_logs"), "Missing daily_logs table");
  });

  it("生成 src/db/client.ts", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["src/db/client.ts"]);
    const content = result.files["src/db/client.ts"];
    assert.ok(content.includes("sql.js"), "Missing sql.js import");
    assert.ok(content.includes("getDb"), "Missing getDb function");
    assert.ok(content.includes("queryAll"), "Missing queryAll helper");
    assert.ok(content.includes("queryOne"), "Missing queryOne helper");
    assert.ok(content.includes("execute"), "Missing execute helper");
    assert.ok(content.includes("initDb"), "Missing initDb function");
  });
});

// ═══════════════════════════════════════════════════════
describe("4 — src/routes/（路由层）", () => {
  it("生成 CRUD 路由文件", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.stats.routes >= 3, `Expected >= 3 routes, got ${result.stats.routes}`);
    assert.ok(result.files["src/routes/pets.ts"], "Missing pets routes");
    assert.ok(result.files["src/routes/schedules.ts"], "Missing schedules routes");
    assert.ok(result.files["src/routes/daily_logs.ts"], "Missing daily_logs routes");
  });

  it("生成 auth 路由", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["src/routes/auth.ts"]);
    const content = result.files["src/routes/auth.ts"];
    assert.ok(content.includes("register"), "Missing register route");
    assert.ok(content.includes("login"), "Missing login route");
    assert.ok(content.includes("/me"), "Missing /me route");
    assert.ok(content.includes("bcrypt"), "Missing bcrypt import");
    assert.ok(content.includes("jwt.sign"), "Missing JWT sign");
  });

  it("路由文件包含 CRUD 方法", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const petsRoute = result.files["src/routes/pets.ts"];
    assert.ok(petsRoute.includes("authenticate"), "Missing auth middleware");
    assert.ok(petsRoute.includes("app.get"), "Missing GET");
    assert.ok(petsRoute.includes("app.post"), "Missing POST");
    assert.ok(petsRoute.includes("app.put"), "Missing PUT");
    assert.ok(petsRoute.includes("app.delete"), "Missing DELETE");
  });
});

// ═══════════════════════════════════════════════════════
describe("5 — src/middleware/（中间件）", () => {
  it("生成 JWT 认证中间件", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["src/middleware/auth.ts"]);
    const content = result.files["src/middleware/auth.ts"];
    assert.ok(content.includes("authenticate"), "Missing authenticate");
    assert.ok(content.includes("requireAdmin"), "Missing requireAdmin");
    assert.ok(content.includes("optionalAuth"), "Missing optionalAuth");
    assert.ok(content.includes("jwtVerify"), "Missing jwtVerify call");
    assert.ok(content.includes("status(401)"), "Missing 401 response");
  });
});

// ═══════════════════════════════════════════════════════
describe("6 — src/services/（服务层）", () => {
  it("生成服务文件", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.stats.services >= 3, `Expected >= 3 services, got ${result.stats.services}`);
    assert.ok(result.files["src/services/pet.ts"], "Missing pet service");
    assert.ok(result.files["src/services/schedule.ts"], "Missing schedule service");
  });

  it("服务文件包含 CRUD 方法", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const svc = result.files["src/services/pet.ts"];
    assert.ok(svc.includes("list()"), "Missing list()");
    assert.ok(svc.includes("getById"), "Missing getById()");
    assert.ok(svc.includes("create"), "Missing create()");
    assert.ok(svc.includes("update"), "Missing update()");
    assert.ok(svc.includes("delete"), "Missing delete()");
    assert.ok(svc.includes("class"), "Missing class definition");
  });
});

// ═══════════════════════════════════════════════════════
describe("7 — src/types/（类型定义）", () => {
  it("生成类型文件", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.files["src/types/index.ts"]);
    const types = result.files["src/types/index.ts"];
    assert.ok(types.includes("export interface"), "Missing interfaces");
  });

  it("包含数据模型类型", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const types = result.files["src/types/index.ts"];
    assert.ok(types.includes("interface Pet"), "Missing Pet type");
    assert.ok(types.includes("interface Schedule"), "Missing Schedule type");
    assert.ok(types.includes("interface User"), "Missing User type");
  });

  it("包含 Create/Update 输入类型", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const types = result.files["src/types/index.ts"];
    assert.ok(types.includes("CreatePetInput"), "Missing CreatePetInput");
    assert.ok(types.includes("UpdatePetInput"), "Missing UpdatePetInput");
  });

  it("包含 Auth 类型", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const types = result.files["src/types/index.ts"];
    assert.ok(types.includes("LoginInput"), "Missing LoginInput");
    assert.ok(types.includes("RegisterInput"), "Missing RegisterInput");
    assert.ok(types.includes("AuthResponse"), "Missing AuthResponse");
  });

  it("包含 JWT payload 类型", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const types = result.files["src/types/index.ts"];
    assert.ok(types.includes("JwtPayload"), "Missing JwtPayload");
  });

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

// ═══════════════════════════════════════════════════════
describe("8 — 测试生成", () => {
  it("生成测试文件", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    assert.ok(result.stats.tests >= 2, `Expected >= 2 test files, got ${result.stats.tests}`);
    assert.ok(result.files["src/__tests__/auth.test.ts"], "Missing auth test");
  });

  it("auth 测试包含 register/login/me", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const test = result.files["src/__tests__/auth.test.ts"];
    assert.ok(test.includes("registers a new user"), "Missing register test");
    assert.ok(test.includes("logs in with correct credentials"), "Missing login test");
    assert.ok(test.includes("returns current user"), "Missing me test");
    assert.ok(test.includes("rejects without token"), "Missing auth fail test");
  });

  it("CRUD 测试包含完整生命周期", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const test = result.files["src/__tests__/pets.test.ts"];
    if (!test) return; // pets test may not exist depending on naming
    assert.ok(test.includes("creates"), "Missing create test");
    assert.ok(test.includes("updates"), "Missing update test");
    assert.ok(test.includes("deletes"), "Missing delete test");
    assert.ok(test.includes("returns 404 after delete"), "Missing 404-after-delete test");
  });
});

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

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

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

  it("空的 databaseSchema 仍然生成基础项目", () => {
    const result = buildBackend(DEMO_PRD, { ...DEMO_ARCH, databaseSchema: [] });
    assert.ok(!result.error);
    assert.ok(result.files["src/index.ts"]);
    assert.ok(result.files["src/routes/auth.ts"]);
    assert.ok(result.stats.totalFiles >= 8, `Expected >= 8 files with empty schema, got ${result.stats.totalFiles}`);
  });
});

// ═══════════════════════════════════════════════════════
describe("10 — 文件写入 I/O", () => {
  it("writeBackend 写入完整项目", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const tmpDir = resolve(WORKSPACE, ".tmp-backend-test");

    writeBackend(result, tmpDir);

    assert.ok(existsSync(resolve(tmpDir, "package.json")));
    assert.ok(existsSync(resolve(tmpDir, "tsconfig.json")));
    assert.ok(existsSync(resolve(tmpDir, ".env.example")));
    assert.ok(existsSync(resolve(tmpDir, "README.md")));
    assert.ok(existsSync(resolve(tmpDir, "src/index.ts")));
    assert.ok(existsSync(resolve(tmpDir, "src/db/schema.ts")));
    assert.ok(existsSync(resolve(tmpDir, "src/db/client.ts")));
    assert.ok(existsSync(resolve(tmpDir, "src/middleware/auth.ts")));
    assert.ok(existsSync(resolve(tmpDir, "src/routes/auth.ts")));
    assert.ok(existsSync(resolve(tmpDir, "src/types/index.ts")));

    // Check at least one route & service file
    const routesDir = resolve(tmpDir, "src/routes");
    const servicesDir = resolve(tmpDir, "src/services");
    assert.ok(existsSync(routesDir));
    assert.ok(existsSync(servicesDir));

    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("11 — npm install & build", () => {
  it("npm install 成功", async () => {
    const generateAndInstall = async () => {
      const result = buildBackend(DEMO_PRD, DEMO_ARCH);
      const tmpDir = resolve(WORKSPACE, ".tmp-backend-build");
      writeBackend(result, tmpDir);

      const { execSync } = await import("node:child_process");
      try {
        execSync("npm install --no-audit --no-fund --prefer-offline", {
          cwd: tmpDir,
          encoding: "utf-8",
          timeout: 120_000,
          stdio: "pipe",
        });
        return { success: true, tmpDir };
      } catch (e) {
        return { success: false, error: e.stderr || e.message, tmpDir };
      }
    };

    const { success, error, tmpDir } = await generateAndInstall();

    if (!success) {
      console.error("npm install failed:", error?.slice(0, 500));
    }

    assert.ok(success, `npm install failed: ${error?.slice(0, 200)}`);

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

  it("npm run build 成功", async () => {
    const generateAndBuild = async () => {
      const result = buildBackend(DEMO_PRD, DEMO_ARCH);
      const tmpDir = resolve(WORKSPACE, ".tmp-backend-build");
      writeBackend(result, tmpDir);

      const { execSync } = await import("node:child_process");
      try {
        execSync("npm install --no-audit --no-fund --prefer-offline", {
          cwd: tmpDir,
          encoding: "utf-8",
          timeout: 120_000,
          stdio: "pipe",
        });
        execSync("npx tsc --noEmit", {
          cwd: tmpDir,
          encoding: "utf-8",
          timeout: 60_000,
          stdio: "pipe",
        });
        return { success: true, tmpDir };
      } catch (e) {
        return { success: false, error: e.stderr || e.message, tmpDir };
      }
    };

    const { success, error, tmpDir } = await generateAndBuild();

    if (!success) {
      console.error("Build failed:", error?.slice(0, 1000));
    }

    assert.ok(success, `Build failed: ${error?.slice(0, 300)}`);

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

// ═══════════════════════════════════════════════════════
describe("12 — CLI 集成", () => {
  it("--prd + --arch CLI 模式", async () => {
    const result = buildBackend(
      loadPRD("ecommerce"),
      loadArch()
    );
    writeBackend(result, resolve(WORKSPACE, ".tmp-cli-backend"));
    assert.ok(!result.error);
    rmSync(resolve(WORKSPACE, ".tmp-cli-backend"), { recursive: true, force: true });
  });

  it("--help 显示帮助", async () => {
    const { execSync } = await import("node:child_process");
    const output = execSync("node scripts/backend-builder-agent.mjs --help", {
      cwd: WORKSPACE,
      encoding: "utf-8",
    });
    assert.ok(output.includes("Backend Builder"));
    assert.ok(output.includes("Usage"));
  });
});

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

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

      assert.ok(!result.error, `${domain}: ${result.message}`);
      assert.ok(result.stats.totalFiles >= 8, `${domain}: expected >= 8 files, got ${result.stats.totalFiles}`);
      assert.ok(result.stats.routes >= 2, `${domain}: expected >= 2 routes, got ${result.stats.routes}`);
      assert.ok(result.stats.services >= 2, `${domain}: expected >= 2 services, got ${result.stats.services}`);
    });
  }
});

// ═══════════════════════════════════════════════════════
describe("14 — 生成的内容非空且有意义", () => {
  it("所有生成的文件内容非空（>50 字符）", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);

    for (const [path, content] of Object.entries(result.files)) {
      assert.ok(content.length > 50, `${path}: too short (${content.length} chars)`);
    }
  });

  it("package.json 可解析为合法 JSON", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const pkg = JSON.parse(result.files["package.json"]);
    assert.ok(pkg.name);
    assert.ok(pkg.dependencies);
    assert.ok(pkg.scripts);
  });

  it("tsconfig.json 可解析为合法 JSON", () => {
    const result = buildBackend(DEMO_PRD, DEMO_ARCH);
    const cfg = JSON.parse(result.files["tsconfig.json"]);
    assert.ok(cfg.compilerOptions);
  });
});
