import Fastify from "fastify";
import { afterAll, beforeEach, describe, expect, it } from "vitest";
import { registerAdminPlugin } from "../../src/admin/plugin.js";
import { ProviderConnectionService } from "../../src/connections/providerConnections.js";
import { mintSessionToken, sessionCookieHeader } from "../../src/admin/routes/authRoutes.js";
import { DEFAULT_ORG_ID, prisma, resetDb, testSecretEnvelope } from "./helpers.js";

const SESSION_SECRET = "integration-test-session-secret";
const secrets = testSecretEnvelope;

async function buildApp() {
  const app = Fastify({ logger: false });
  await registerAdminPlugin(app, {
    prisma,
    sessionSecret: SESSION_SECRET,
    publicBaseUrl: "http://127.0.0.1:8788",
    feishuAppId: "cli_test",
    feishuAppSecret: "secret_test",
    projectWorkspaceRoot: "/tmp/cph-test-workspaces",
    secretEnvelope: secrets,
    providerReadinessProbe: async () => {},
    cookieSecure: false,
  });
  await app.ready();
  return app;
}

async function seedUser(role: "OWNER" | "ADMIN" | "MEMBER"): Promise<string> {
  const id = `u-${role.toLowerCase()}`;
  const openId = `ou_${role.toLowerCase()}`;
  await prisma.user.create({ data: { id, feishuOpenId: openId, displayName: id } });
  await prisma.organizationMembership.create({
    data: { organizationId: DEFAULT_ORG_ID, userId: id, role },
  });
  return mintSessionToken({ userId: id, feishuOpenId: openId }, SESSION_SECRET);
}

describe("Organization BYOK provider connections", () => {
  beforeEach(async () => {
    await resetDb();
  });

  afterAll(async () => {
    await prisma.$disconnect();
  });

  it("stores write-only credentials in an encrypted immutable version", async () => {
    const token = await seedUser("OWNER");
    const app = await buildApp();
    const credential = {
      baseUrl: "https://provider.example/api",
      authToken: "org-a-auth-token-never-return",
      anthropicApiKey: "org-a-api-key-never-return",
    };
    try {
      const response = await app.inject({
        method: "PUT",
        url: "/api/org/test-default/provider-connections/openrouter",
        headers: {
          cookie: sessionCookieHeader(token),
          "content-type": "application/json",
        },
        payload: credential,
      });

      expect(response.statusCode).toBe(201);
      expect(response.json()).toMatchObject({
        providerId: "openrouter",
        mode: "BYOK",
        status: "ACTIVE",
        activeVersion: 1,
        keyId: "test-active",
      });
      expect(response.body).not.toContain(credential.authToken);
      expect(response.body).not.toContain(credential.anthropicApiKey);
      expect(response.body).not.toContain(credential.baseUrl);

      const persisted = await prisma.$queryRaw<Array<{
        envelope: unknown;
        activeSecretVersionId: string;
      }>>`
        SELECT v."envelope", c."activeSecretVersionId"
        FROM "ProviderCredentialVersion" v
        JOIN "OrganizationProviderConnection" c ON c."id" = v."connectionId"
        WHERE c."organizationId" = ${DEFAULT_ORG_ID} AND c."providerId" = 'openrouter'
      `;
      expect(persisted).toHaveLength(1);
      const serialized = JSON.stringify(persisted);
      expect(serialized).not.toContain(credential.authToken);
      expect(serialized).not.toContain(credential.anthropicApiKey);
      expect(serialized).not.toContain(credential.baseUrl);
      expect(persisted[0]?.activeSecretVersionId).toBeTruthy();
    } finally {
      await app.close();
    }
  });

  it("rotates by appending a version and never exposes old or new plaintext", async () => {
    const token = await seedUser("ADMIN");
    const app = await buildApp();
    const cookie = sessionCookieHeader(token);
    try {
      const first = await app.inject({
        method: "PUT",
        url: "/api/org/test-default/provider-connections/openrouter",
        headers: { cookie, "content-type": "application/json" },
        payload: { baseUrl: "https://one.example", authToken: "first-secret" },
      });
      const second = await app.inject({
        method: "PUT",
        url: "/api/org/test-default/provider-connections/openrouter",
        headers: { cookie, "content-type": "application/json" },
        payload: { baseUrl: "https://two.example", authToken: "second-secret" },
      });

      expect(first.statusCode).toBe(201);
      expect(second.statusCode).toBe(200);
      expect(second.json()).toMatchObject({ activeVersion: 2, status: "ACTIVE" });
      const versions = await prisma.$queryRaw<Array<{ version: number; retiredAt: Date | null }>>`
        SELECT v."version", v."retiredAt"
        FROM "ProviderCredentialVersion" v
        JOIN "OrganizationProviderConnection" c ON c."id" = v."connectionId"
        WHERE c."organizationId" = ${DEFAULT_ORG_ID}
        ORDER BY v."version"
      `;
      expect(versions).toEqual([
        { version: 1, retiredAt: expect.any(Date) },
        { version: 2, retiredAt: null },
      ]);

      const read = await app.inject({
        method: "GET",
        url: "/api/org/test-default/provider-connections",
        headers: { cookie },
      });
      expect(read.statusCode).toBe(200);
      expect(read.json()).toEqual({ connections: [expect.objectContaining({ activeVersion: 2 })] });
      expect(read.body).not.toContain("first-secret");
      expect(read.body).not.toContain("second-secret");
    } finally {
      await app.close();
    }
  });

  it("rejects members and refuses to overwrite a platform-managed connection", async () => {
    const memberToken = await seedUser("MEMBER");
    const app = await buildApp();
    try {
      const denied = await app.inject({
        method: "PUT",
        url: "/api/org/test-default/provider-connections/openrouter",
        headers: {
          cookie: sessionCookieHeader(memberToken),
          "content-type": "application/json",
        },
        payload: { baseUrl: "https://provider.example", authToken: "denied-secret" },
      });
      expect(denied.statusCode).toBe(403);
      const service = new ProviderConnectionService(prisma, secrets, async () => {});
      await expect(service.rotateByok({
        organizationId: DEFAULT_ORG_ID,
        providerId: "another-provider",
        actorUserId: "u-member",
        baseUrl: "https://provider.example",
        authToken: "service-bypass-secret",
      })).rejects.toThrow("requires an active Organization OWNER or ADMIN");
      await expect(prisma.organizationProviderConnection.count({
        where: { organizationId: DEFAULT_ORG_ID, providerId: "another-provider" },
      })).resolves.toBe(0);

      await prisma.$executeRaw`
        INSERT INTO "OrganizationProviderConnection"
          ("id", "organizationId", "providerId", "mode", "status", "createdAt", "updatedAt")
        VALUES
          ('platform-connection', ${DEFAULT_ORG_ID}, 'openrouter', 'PLATFORM_MANAGED', 'DRAFT', NOW(), NOW())
      `;
      const ownerToken = await seedUser("OWNER");
      const refused = await app.inject({
        method: "PUT",
        url: "/api/org/test-default/provider-connections/openrouter",
        headers: {
          cookie: sessionCookieHeader(ownerToken),
          "content-type": "application/json",
        },
        payload: { baseUrl: "https://provider.example", authToken: "takeover-secret" },
      });
      expect(refused.statusCode).toBe(403);
      expect(refused.json()).toMatchObject({
        error: { code: "forbidden" },
      });
    } finally {
      await app.close();
    }
  });

  it("does not activate or persist a credential that fails readiness", async () => {
    await seedUser("ADMIN");
    const service = new ProviderConnectionService(prisma, secrets, async () => {
      throw new Error("provider credential readiness check failed: status 401");
    });

    await expect(service.rotateByok({
      organizationId: DEFAULT_ORG_ID,
      providerId: "openrouter",
      actorUserId: "u-admin",
      baseUrl: "https://provider.example/api",
      authToken: "rejected-secret",
    })).rejects.toThrow("readiness check failed");
    await expect(prisma.organizationProviderConnection.count({
      where: { organizationId: DEFAULT_ORG_ID, providerId: "openrouter" },
    })).resolves.toBe(0);
    await expect(prisma.providerCredentialVersion.count()).resolves.toBe(0);
  });
});
