import { appendFileSync, cpSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { sql } from "drizzle-orm";
import { createPostgresJsClient } from "@platform-modules/db/postgres/postgres-js";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
  startControlPlane,
  type RunningControlPlane,
} from "../../apps/control-plane/src/server.js";
import { createPostgresRuntime, schema } from "@awp/persistence";

const requiredEnvironment = (name: string): string => {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`${name} is required for the destructive PostgreSQL smoke`);
  return value;
};

const suite = process.env.AWP_DESTRUCTIVE_SMOKE === "1" ? describe : describe.skip;
const migrationsFolder = fileURLToPath(
  new URL("../../packages/persistence/drizzle/", import.meta.url),
);

async function json(response: Response): Promise<Record<string, unknown>> {
  return (await response.json()) as Record<string, unknown>;
}

suite("control-plane PostgreSQL transport smoke (non-GOLIVE)", () => {
  let running: RunningControlPlane;
  let baseUrl: string;
  let databaseUrl: string;
  let projectId = "";

  beforeAll(async () => {
    databaseUrl = requiredEnvironment("AWP_TEST_POSTGRES_URL");
    const admin = createPostgresJsClient({ connectionString: databaseUrl, schema });
    await admin.execute(sql.raw("drop schema if exists drizzle cascade"));
    await admin.execute(sql.raw("drop schema public cascade"));
    await admin.execute(sql.raw("create schema public"));
    await admin.$client.end({ timeout: 5 });
    running = await startControlPlane({
      databaseUrl: databaseUrl!,
      migrationsFolder,
      port: 0,
    });
    baseUrl = `http://127.0.0.1:${running.port}`;
  }, 30_000);

  afterAll(async () => {
    await running?.close();
  });

  it("reports healthy with every repository migration applied and no drift", async () => {
    const response = await fetch(`${baseUrl}/health`);
    expect(response.status).toBe(200);
    const body = await json(response);
    expect(body).toMatchObject({
      status: "ok",
      database: "postgresql",
      migrations: { pending: 0, drift: [] },
    });
    const migrations = body.migrations as { applied: number; expected: number };
    expect(migrations.applied).toBe(migrations.expected);
    expect(migrations.expected).toBeGreaterThan(0);
  });

  it("reports repository migration hash drift", async () => {
    const alteredFolder = mkdtempSync(join(tmpdir(), "awp-migration-drift-"));
    try {
      cpSync(migrationsFolder, alteredFolder, { recursive: true });
      appendFileSync(join(alteredFolder, "0003_massive_mastermind.sql"), "\n-- drift proof\n");
      const driftRuntime = createPostgresRuntime(databaseUrl!, alteredFolder);
      const status = await driftRuntime.migrationStatus();
      expect(status.pending).toBeGreaterThan(0);
      expect(status.drift).toEqual([expect.stringMatching(/hash differs from repository/)]);
      await driftRuntime.close();
    } finally {
      rmSync(alteredFolder, { recursive: true, force: true });
    }
  });

  it("persists authoring state through Hono routes and rejects a cycle", async () => {
    const projectResponse = await fetch(`${baseUrl}/internal/projects`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        name: "AWP PostgreSQL transport smoke",
        repositoryUrl: "https://github.com/platform-modules/awp.git",
      }),
    });
    expect(projectResponse.status).toBe(201);
    const project = (await json(projectResponse)).project as { id: string };
    projectId = project.id;

    const visionResponse = await fetch(`${baseUrl}/internal/projects/${projectId}/vision`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ summary: "Ship one trustworthy software-delivery journey." }),
    });
    expect(visionResponse.status).toBe(201);

    const goalResponse = await fetch(`${baseUrl}/internal/projects/${projectId}/goals`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        title: "GOLIVE",
        successCriteria: ["The real Project to trusted Merge journey passes."],
      }),
    });
    expect(goalResponse.status).toBe(201);

    const planResponse = await fetch(`${baseUrl}/internal/projects/${projectId}/plans`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        title: "First launch plan",
        taskTitles: ["Prepare transport smoke", "Change transport smoke", "Verify transport smoke"],
      }),
    });
    expect(planResponse.status).toBe(201);
    const created = await json(planResponse);
    const plan = created.plan as { id: string };
    expect(plan.id).toBeTruthy();
    const tasks = created.tasks as Array<{ id: string; title: string }>;
    expect(tasks.map((task) => task.title)).toEqual([
      "Prepare transport smoke",
      "Change transport smoke",
      "Verify transport smoke",
    ]);

    const dependencyResponse = await fetch(
      `${baseUrl}/internal/projects/${projectId}/tasks/${tasks[1]!.id}/dependencies`,
      {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ prerequisiteTaskId: tasks[0]!.id }),
      },
    );
    expect(dependencyResponse.status).toBe(200);

    const cycleResponse = await fetch(
      `${baseUrl}/internal/projects/${projectId}/tasks/${tasks[0]!.id}/dependencies`,
      {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ prerequisiteTaskId: tasks[1]!.id }),
      },
    );
    expect(cycleResponse.status).toBe(409);
    const cycle = await json(cycleResponse);
    expect(cycle).toMatchObject({ error: "dependency-cycle" });
    expect(String(cycle.message)).toContain(tasks[0]!.id);
    expect(String(cycle.message)).toContain(tasks[1]!.id);

    const hierarchyResponse = await fetch(`${baseUrl}/internal/projects/${projectId}`);
    expect(hierarchyResponse.status).toBe(200);
    const hierarchy = await json(hierarchyResponse);
    expect(hierarchy).toMatchObject({
      project: {
        id: projectId,
        repositoryUrl: "https://github.com/platform-modules/awp.git",
      },
    });
    const persistedTasks = hierarchy.tasks as Array<{
      id: string;
      position: number;
      dependencyIds: string[];
    }>;
    expect(persistedTasks.map((task) => task.position)).toEqual([0, 1, 2]);
    expect(persistedTasks.find((task) => task.id === tasks[1]!.id)?.dependencyIds).toEqual([
      tasks[0]!.id,
    ]);
    expect(persistedTasks.find((task) => task.id === tasks[0]!.id)?.dependencyIds).toEqual([]);
  });

  it("survives a control-plane restart with the lifecycle hierarchy unchanged", async () => {
    const before = await json(await fetch(`${baseUrl}/internal/projects/${projectId}`));
    await running.close();
    running = await startControlPlane({
      databaseUrl: databaseUrl!,
      migrationsFolder,
      port: 0,
    });
    baseUrl = `http://127.0.0.1:${running.port}`;
    const after = await json(await fetch(`${baseUrl}/internal/projects/${projectId}`));
    expect(after).toEqual(before);
  }, 30_000);
});
