import { readdirSync, readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { sql } from "drizzle-orm";
import { createPgliteClient } from "@platform-modules/db/postgres/pglite";
import { describe, expect, it } from "vitest";
import {
  ConfigurationAuthorityService,
  I1LifecycleService,
  SYSTEM_CONFIGURATION_SCOPE_ID,
} from "@awp/application";
import { I1_CONFIGURATION_KEYS } from "@awp/config";
import {
  authorityContext,
  unsafeOpaqueId,
  type AwpId,
  type CorrelationId,
  type OperationId,
  type PlanId,
  type PlanRevisionId,
  type PrincipalId,
  type ProjectId,
  type TaskId,
} from "@awp/contracts";
import { PostgresUnitOfWork, schema } from "@awp/persistence";

const migrationDirectory = fileURLToPath(
  new URL("../../packages/persistence/drizzle/", import.meta.url),
);

async function database() {
  const db = createPgliteClient({ schema });
  for (const name of readdirSync(migrationDirectory)
    .filter((name) => name.endsWith(".sql"))
    .sort()) {
    const migration = readFileSync(`${migrationDirectory}/${name}`, "utf8");
    for (const statement of migration.split("--> statement-breakpoint")) {
      const sqlText = statement.trim();
      if (sqlText.length > 0) await db.execute(sql.raw(sqlText));
    }
  }
  return db;
}

function testContext(projectId?: ProjectId) {
  const principal = {
    id: unsafeOpaqueId<PrincipalId>("principal:owner:configuration"),
    kind: "human" as const,
    capabilities: [],
  };
  return {
    operationId: unsafeOpaqueId<OperationId>("operation:configuration"),
    correlationId: unsafeOpaqueId<CorrelationId>("correlation:configuration"),
    idempotencyKey: "configuration-test",
    authority: authorityContext(principal, [], projectId),
  };
}

function idGenerator() {
  let sequence = 0;
  return {
    next<T extends AwpId>(): T {
      sequence += 1;
      return unsafeOpaqueId<T>(`configuration-id-${sequence}`);
    },
  };
}

async function insertDraftPlan(
  uow: PostgresUnitOfWork,
  projectId: ProjectId,
  suffix: string,
): Promise<{ planId: PlanId; revisionId: PlanRevisionId }> {
  const planId = unsafeOpaqueId<PlanId>(`plan:${suffix}`);
  const revisionId = unsafeOpaqueId<PlanRevisionId>(`plan-revision:${suffix}`);
  const taskId = unsafeOpaqueId<TaskId>(`task:${suffix}`);
  await uow.transaction(async (tx) => {
    await tx.plans.insert({
      id: planId,
      projectId,
      title: `Plan ${suffix}`,
      status: "draft",
      revision: 1,
    });
    await tx.planRevisions.insert({
      id: revisionId,
      planId,
      projectId,
      sequence: 1,
      title: `Plan ${suffix}`,
      goalIds: [],
    });
    await tx.tasks.insert({
      id: taskId,
      projectId,
      planRevisionId: revisionId,
      title: `Task ${suffix}`,
      position: 0,
      status: "planned",
      dependencyIds: [],
      goalIds: [],
      revision: 1,
    });
  });
  return { planId, revisionId };
}

describe("I1 PostgreSQL configuration authority", () => {
  it("persists built-in definitions, resolves scoped overrides and audits mutations", async () => {
    const db = await database();
    const uow = new PostgresUnitOfWork(db);
    const projectId = unsafeOpaqueId<ProjectId>("project:configuration");
    const clock = { now: () => new Date("2026-08-24T03:00:00.000Z") };
    const configuration = new ConfigurationAuthorityService(uow, idGenerator(), clock);
    await configuration.ensureDefinitions();

    expect((await configuration.listDefinitions()).map((definition) => definition.key)).toEqual([
      I1_CONFIGURATION_KEYS.executionDefaultModel,
      I1_CONFIGURATION_KEYS.executionEnabled,
    ]);

    await configuration.setOverride({
      definitionKey: I1_CONFIGURATION_KEYS.executionDefaultModel,
      scopeType: "system",
      scopeId: SYSTEM_CONFIGURATION_SCOPE_ID,
      value: "system-model",
      context: testContext(),
    });
    const projectOverride = await configuration.setOverride({
      definitionKey: I1_CONFIGURATION_KEYS.executionDefaultModel,
      scopeType: "project",
      scopeId: projectId,
      value: "project-model",
      context: testContext(projectId),
    });
    expect(projectOverride.resourceRevision).toBe(1);

    const effective = await configuration.resolve(I1_CONFIGURATION_KEYS.executionDefaultModel, [
      { scopeType: "system", scopeId: SYSTEM_CONFIGURATION_SCOPE_ID },
      { scopeType: "project", scopeId: projectId },
    ]);
    expect(effective).toMatchObject({
      value: "project-model",
      sourceScope: "project",
      sourceId: projectId,
    });

    await configuration.setOverride({
      definitionKey: I1_CONFIGURATION_KEYS.executionDefaultModel,
      scopeType: "project",
      scopeId: projectId,
      value: "project-model-v2",
      context: testContext(projectId),
    });
    expect(
      (await configuration.listOverrides(I1_CONFIGURATION_KEYS.executionDefaultModel)).find(
        (override) => override.scopeType === "project",
      )?.resourceRevision,
    ).toBe(2);

    const audits = await db.select().from(schema.auditRecords);
    expect(audits.filter((record) => record.action === "ConfigurationOverrideSet")).toHaveLength(3);
    expect(audits.every((record) => !("value" in (record.safeMetadata as object)))).toBe(true);
    await db.$client.close();
  }, 20_000);

  it("changes the next FactoryRun without restart and exposes a real project off-switch", async () => {
    const db = await database();
    const uow = new PostgresUnitOfWork(db);
    const projectId = unsafeOpaqueId<ProjectId>("project:configuration-lifecycle");
    const ids = idGenerator();
    const clock = { now: () => new Date("2026-08-24T03:30:00.000Z") };
    const configuration = new ConfigurationAuthorityService(uow, ids, clock);
    await configuration.ensureDefinitions();
    const lifecycle = new I1LifecycleService(uow, ids, clock, undefined, configuration);

    await uow.transaction((tx) =>
      tx.projects.insert({
        id: projectId,
        name: "Configuration lifecycle",
        repositoryUrl: "https://example.com/configuration.git",
        requiredChecks: [],
        status: "active",
        revision: 1,
      }),
    );

    await configuration.setOverride({
      definitionKey: I1_CONFIGURATION_KEYS.executionDefaultModel,
      scopeType: "project",
      scopeId: projectId,
      value: "model-one",
      context: testContext(projectId),
    });
    const first = await insertDraftPlan(uow, projectId, "one");
    await lifecycle.approvePlan(projectId, first.planId, testContext(projectId), {
      accountId: "account-owner",
    });
    let runs = await uow.transaction((tx) => tx.factoryRuns.listByProject(projectId));
    expect(runs.find((run) => run.planRevisionId === first.revisionId)).toMatchObject({
      accountId: "account-owner",
      model: "model-one",
    });

    await configuration.setOverride({
      definitionKey: I1_CONFIGURATION_KEYS.executionDefaultModel,
      scopeType: "project",
      scopeId: projectId,
      value: "model-two",
      context: testContext(projectId),
    });
    const second = await insertDraftPlan(uow, projectId, "two");
    await lifecycle.approvePlan(projectId, second.planId, testContext(projectId), {
      accountId: "account-owner",
    });
    runs = await uow.transaction((tx) => tx.factoryRuns.listByProject(projectId));
    expect(runs.find((run) => run.planRevisionId === second.revisionId)?.model).toBe("model-two");
    expect(runs.find((run) => run.planRevisionId === first.revisionId)?.model).toBe("model-one");

    await configuration.setOverride({
      definitionKey: I1_CONFIGURATION_KEYS.executionEnabled,
      scopeType: "project",
      scopeId: projectId,
      value: false,
      context: testContext(projectId),
    });
    const blocked = await insertDraftPlan(uow, projectId, "blocked");
    await expect(
      lifecycle.approvePlan(projectId, blocked.planId, testContext(projectId), {
        accountId: "account-owner",
      }),
    ).rejects.toThrow(/execution is disabled by configuration/);
    runs = await uow.transaction((tx) => tx.factoryRuns.listByProject(projectId));
    expect(runs.some((run) => run.planRevisionId === blocked.revisionId)).toBe(false);

    await db.$client.close();
  }, 20_000);
});
