import { describe, expect, it } from "@effect/vitest";
import { ConnectionId, ScopeId, SecretId } from "@executor-js/sdk";

import {
  effectiveBindingForScope,
  missingCredentialLabels,
  type BindingRowForCredentialStatus,
  type SourceForCredentialStatus,
} from "./credential-status";

const userScope = ScopeId.make("user");
const orgScope = ScopeId.make("org");
const scopeRanks = new Map([
  [userScope, 0],
  [orgScope, 1],
]);

const source: SourceForCredentialStatus = {
  config: {
    headers: {
      Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " },
    },
    oauth2: {
      securitySchemeName: "oauth2",
      flow: "clientCredentials",
      clientIdSlot: "oauth2:oauth2:client-id",
      clientSecretSlot: "oauth2:oauth2:client-secret",
      connectionSlot: "oauth2:oauth2:connection",
    },
  },
};

const bindings = (
  scopeId: ScopeId,
  slots: readonly string[],
): readonly BindingRowForCredentialStatus[] =>
  slots.map((slot) => ({
    slot,
    scopeId,
    value:
      slot === "oauth2:oauth2:connection"
        ? {
            kind: "connection",
            connectionId: ConnectionId.make(`${scopeId}-connection`),
          }
        : {
            kind: "secret",
            secretId: SecretId.make(`${scopeId}-${slot}`),
          },
  }));

const allSlots = [
  "header:authorization",
  "oauth2:oauth2:client-id",
  "oauth2:oauth2:client-secret",
  "oauth2:oauth2:connection",
] as const;

describe("OpenAPI credential status", () => {
  it("treats personal bindings as satisfying the user's credential status for an org source", () => {
    expect(
      missingCredentialLabels(source, bindings(userScope, allSlots), userScope, scopeRanks, {
        liveConnectionIds: [ConnectionId.make("user-connection")],
      }),
    ).toEqual([]);
  });

  it("falls back to shared org bindings when the user has no personal override", () => {
    expect(
      missingCredentialLabels(source, bindings(orgScope, allSlots), userScope, scopeRanks, {
        liveConnectionIds: [ConnectionId.make("org-connection")],
      }),
    ).toEqual([]);
  });

  it("treats a stale connection binding as missing OAuth credentials", () => {
    expect(
      missingCredentialLabels(source, bindings(userScope, allSlots), userScope, scopeRanks, {
        liveConnectionIds: [],
      }),
    ).toEqual(["OAuth client connection"]);
  });

  it("does not treat personal bindings as satisfying org-level credential status", () => {
    expect(
      missingCredentialLabels(source, bindings(userScope, allSlots), orgScope, scopeRanks),
    ).toEqual(["Authorization", "Client ID", "Client Secret", "OAuth client connection"]);
  });

  it("prefers the personal binding over a shared org binding", () => {
    const binding = effectiveBindingForScope(
      [
        ...bindings(orgScope, ["oauth2:oauth2:connection"]),
        ...bindings(userScope, ["oauth2:oauth2:connection"]),
      ],
      "oauth2:oauth2:connection",
      userScope,
      scopeRanks,
    );

    expect(binding?.scopeId).toEqual(userScope);
  });
});
