/**
 * Per-organization credential payloads, shared between server and UI.
 *
 * A credential row is canonically identified by the
 * `(role, backend, auth_method)` tuple; the schema stores the tuple,
 * not a dotted-string discriminator. The dotted form
 * `<role>.<backend>.<auth-method>` clients see in `type` is a derived
 * label projected from the tuple at DTO-build time. Always 3 segments
 * — even when a backend has a single canonical auth method, it's
 * recorded explicitly so the wire format and the schema stay in
 * lockstep without a per-backend special case.
 *
 * Each row carries `{ secrets, metadata }`. The database enforces the
 * split at the column level: `secrets` is encrypted, `metadata` is
 * plaintext jsonb. The union below forces every new field to pick a
 * side at authoring time — a future column cannot accidentally stuff
 * a secret into `metadata` without the TS compiler flagging it,
 * because the keys in each variant's `metadata` are fixed.
 *
 * The registry (`packages/server/src/db/repository/credential/registry.ts`)
 * owns the matching zod validators, the runtime-plaintext-location
 * pin, and the cardinality (`'singleton' | 'multi' | 'per-user'`).
 * This file only expresses the shape both sides of the wire agree on.
 *
 *  - `provisioning.*` — admin credentials a worker uses to create
 *    per-app logical databases in a customer's cloud.
 *  - `inference.*` — credentials used to call a managed inference API
 *    (Snowflake Cortex REST API, Databricks AI Gateway). Non-singleton
 *    so an org can keep separate dev/prod rows.
 *  - reserved: `authoring.*`, `runtime.*`, `connection.*`, `pat.*`,
 *    `trust.*`, `generator.*`.
 *
 * Trust and generator payload variants don't appear in the union yet —
 * those land in the followup PR alongside their registry entries. The
 * schema (and the CHECK constraint allowing `class IN ('stored', 'trust',
 * 'generator')`) already supports them so the followup is registry-only.
 */

/**
 * Secrets shape for `provisioning.snowflake.pat` and
 * `inference.snowflake-cortex.pat`. Snowflake accepts either a
 * programmatic access token (used as the `password` for `snowflake-sdk`,
 * or as a bearer token against the REST API) or an RSA private key used
 * to sign a short-lived JWT. Consumers branch on which shape is present.
 *
 * Note that the dotted label says `pat` even when the keypair branch is
 * used; `pat` here is the identifier of the auth-method *family*,
 * because Snowflake exposes both as a single endpoint that consumes a
 * bearer token (the keypair JWT and the literal PAT are interchangeable
 * at the request level). When a true second auth method lands (e.g.
 * OAuth M2M) it gets its own tuple with a distinct auth_method segment.
 */
export type SnowflakeSecrets = { pat: string } | { privateKey: string; privateKeyPassphrase?: string };

export type ProvisioningLakebasePayload = {
  type: 'provisioning.lakebase.pat';
  secrets: { pat: string };
  metadata: {
    workspaceUrl?: string;
    adminHost?: string;
    adminPort?: number;
    adminUsername?: string;
    sslMode?: string;
  };
};

export type ProvisioningSnowflakePayload = {
  type: 'provisioning.snowflake.pat';
  secrets: SnowflakeSecrets;
  metadata: {
    account: string;
    username: string;
    warehouse?: string;
    role?: string;
    schema?: string;
    databasePrefix?: string;
  };
};

/**
 * Credentials used to call the Snowflake Cortex REST API. Same secret
 * shapes as `provisioning.snowflake.pat` — the REST API accepts either
 * a long-lived PAT as a bearer token or a short-lived JWT signed with
 * an RSA private key. `cardinality: 'multi'` in the registry because
 * orgs will plausibly want separate dev / prod Cortex identities.
 */
export type InferenceSnowflakeCortexPayload = {
  type: 'inference.snowflake-cortex.pat';
  secrets: SnowflakeSecrets;
  metadata: {
    /** Snowflake account identifier, e.g. `xy12345.us-east-1`. */
    account: string;
    /** Service user whose PAT or public key is installed on the Snowflake side. */
    username: string;
    /** Role the PAT is scoped to / the JWT should assume at call time. */
    role?: string;
  };
};

/**
 * Databricks AI Gateway credential. AI Gateway only accepts Programmatic
 * Access Tokens (PATs) as bearer credentials — there is no keypair or
 * OAuth path comparable to Snowflake Cortex. `gatewayHost` is either the
 * Anthropic-compatible AI Gateway origin or a workspace AI Gateway
 * Anthropic messages URL, both validated at the registry.
 */
export type InferenceDatabricksAiGatewayPayload = {
  type: 'inference.databricks-ai-gateway.pat';
  secrets: { pat: string };
  metadata: {
    /**
     * AI Gateway origin, e.g. `https://<gateway-name>.ai-gateway.cloud.databricks.com`,
     * or workspace Anthropic messages URL, e.g.
     * `https://<your-databricks-workspace-url>/ai-gateway/anthropic/v1/messages`.
     */
    gatewayHost: string;
  };
};

export type CredentialPayload =
  | ProvisioningLakebasePayload
  | ProvisioningSnowflakePayload
  | InferenceSnowflakeCortexPayload
  | InferenceDatabricksAiGatewayPayload;

export type CredentialType = CredentialPayload['type'];

/**
 * All known `<role>` prefixes. Used by callers to filter credentials by
 * role (e.g. "list all provisioning credentials for this org"). Includes
 * reserved-but-unused roles so adding a new one later is a registry +
 * union change rather than a cross-cutting rename.
 *
 * `trust` and `generator` are reserved for the credential-class taxonomy
 * (see `CredentialClass` below): trust-config rows like
 * `trust.aws.iam-role-assume` will claim `role: 'trust'`, generator rows
 * like `generator.snowflake.keypair-jwt` will claim `role: 'generator'`.
 * They are not yet populated in the registry — these reserved values
 * just keep the `(class, role, backend, auth_method)` tuple consistent
 * so the followup PR's registrations are a code-only change.
 */
export const CREDENTIAL_ROLES = ['provisioning', 'authoring', 'runtime', 'connection', 'inference', 'pat', 'trust', 'generator'] as const;
export type CredentialRole = (typeof CREDENTIAL_ROLES)[number];

/**
 * Three credential classes, recorded on every row of the `credential`
 * table:
 *   - `stored`     — encrypted secret bytes are the credential
 *                    (PATs, RSA private keys held for JWT signing).
 *   - `trust`      — the credential is a trust assertion held in
 *                    `metadata`; `encrypted_secrets` is NULL. The DB's
 *                    `chk_credential_secrets_class` CHECK constraint
 *                    enforces this.
 *   - `generator`  — encrypted bytes are *generator material* (OAuth
 *                    client_id+secret, RSA keypair) used to mint a
 *                    short-lived runtime token. The minted token is
 *                    never persisted; only the cache key tuple
 *                    (orgId, credentialId, fingerprint, derivationKey)
 *                    is.
 *
 * The class is denormalized onto the row so query paths can branch on
 * it without joining the registry, and so the schema CHECK constraint
 * can enforce the secrets-vs-no-secrets invariant. Only `'stored'`
 * entries are registered today; `'trust'` and `'generator'` are
 * reserved by the schema and the type system, awaiting their registry
 * additions in the followup PR.
 */
export const CREDENTIAL_CLASSES = ['stored', 'trust', 'generator'] as const;
export type CredentialClass = (typeof CREDENTIAL_CLASSES)[number];

/**
 * The process where a credential's plaintext (or its derived runtime
 * token, for generator credentials) may legitimately exist.
 *
 *   - `server`        — the Superblocks control plane.
 *   - `orchestrator`  — the customer's On-Premise Agent.
 *   - `worker`        — the orchestrator's per-language sandboxed
 *                       worker process (Go/JS/Python).
 *   - `customer-sm`   — held in the customer's own secret manager
 *                       (AWS Secrets Manager, GCP Secret Manager,
 *                       Vault); the Superblocks control plane never
 *                       touches it. Reserved.
 *
 * The repository's `assertProcessCanHandle` guard reads a process-level
 * `PROCESS_LOCATION` constant and rejects with
 * `WrongProcessForCredentialError` if a row's
 * `runtimePlaintextLocation` excludes the current process. Replaces
 * the implicit "this code path lives server-side, so the secret must
 * be decryptable here" convention with a typed check.
 */
export const RUNTIME_PLAINTEXT_LOCATIONS = ['server', 'orchestrator', 'worker', 'customer-sm'] as const;
export type RuntimePlaintextLocation = (typeof RUNTIME_PLAINTEXT_LOCATIONS)[number];

/**
 * Format a `(role, backend, authMethod)` tuple into the dotted
 * `CredentialType` label that clients see on the wire. The schema
 * stores the tuple; this is the canonical projection for DTOs and
 * registry-by-label lookups. Always 3 segments.
 */
export function formatCredentialType(role: string, backend: string, authMethod: string): CredentialType {
  return `${role}.${backend}.${authMethod}` as CredentialType;
}

/**
 * Inverse of `formatCredentialType`. Returns `null` when the input
 * doesn't have exactly three dotted segments — the caller decides
 * whether that's a hard error or a soft signal. Backend and auth-method
 * identifiers may contain hyphens but never dots, so a length-3 split
 * is unambiguous.
 */
export function parseCredentialType(type: string): { role: string; backend: string; authMethod: string } | null {
  const parts = type.split('.');
  if (parts.length !== 3) {
    return null;
  }
  return { role: parts[0], backend: parts[1], authMethod: parts[2] };
}

/**
 * Convenience: extract the role segment of a `CredentialType`. The
 * full tuple round-trip is `parseCredentialType`.
 */
export function credentialRole(type: CredentialType): CredentialRole {
  return type.split('.')[0] as CredentialRole;
}

/**
 * Non-secret projection returned from list / GET endpoints. Never contains
 * `secrets`. `metadata` is the same shape the client POSTed under that key.
 *
 * `class`, `role`, `backend`, `authMethod`, and `runtimePlaintextLocation`
 * come from columns on the row (the canonical tuple plus the registry-
 * pinned class and process-location). `type` is the dotted label
 * projected from `(role, backend, authMethod)` — kept on the DTO as
 * an API-stable identifier so existing clients don't have to assemble
 * it themselves.
 */
export interface CredentialDto {
  id: string;
  organizationId: string;
  /** Derived label `${role}.${backend}.${authMethod}`; not stored. */
  type: CredentialType;
  /** `null` when the registry marks the tuple as a singleton. */
  name: string | null;
  metadata: CredentialPayload['metadata'];
  /** First 8 hex of SHA-256 over the decrypted secrets object at write time. */
  fingerprint: string;
  /** See `CredentialClass`. Pinned by the registry on write. */
  class: CredentialClass;
  /** See `CREDENTIAL_ROLES`. First segment of the canonical tuple. */
  role: CredentialRole;
  /** Short backend identifier, e.g. `lakebase`, `snowflake`, `aws`. */
  backend: string;
  /** Short auth-method identifier, e.g. `pat`, `keypair`, `oauth-m2m`. */
  authMethod: string;
  /** See `RuntimePlaintextLocation`. Pinned by the registry on write. */
  runtimePlaintextLocation: RuntimePlaintextLocation;
  createdAt: string;
  updatedAt: string;
  createdBy: string | null;
}

export interface UpsertCredentialRequest {
  type: CredentialType;
  name?: string | null;
  secrets: CredentialPayload['secrets'];
  metadata: CredentialPayload['metadata'];
}
