import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
import { Schema } from "effect";
import {
  ConnectionId,
  CredentialBindingRef,
  InternalError,
  ScopeId,
  SecretBackedValue,
} from "@executor-js/sdk/shared";

import { OpenApiParseError, OpenApiExtractionError, OpenApiOAuthError } from "../sdk/errors";
import { SpecPreview } from "../sdk/preview";
import { StoredSourceSchema } from "../sdk/source-contracts";
import { OAuth2SourceConfig } from "../sdk/types";

// ---------------------------------------------------------------------------
// Params
// ---------------------------------------------------------------------------

const DomainErrors = [
  InternalError,
  OpenApiParseError,
  OpenApiExtractionError,
  OpenApiOAuthError,
] as const;

const ScopeIdParam = {
  scopeId: ScopeId,
};

const SourceParams = {
  scopeId: ScopeId,
  namespace: Schema.String,
};

const OpenApiSpecInputPayload = Schema.Union([
  Schema.Struct({ kind: Schema.Literal("url"), url: Schema.String }),
  Schema.Struct({ kind: Schema.Literal("blob"), value: Schema.String }),
]);

const PreviewSpecFetchCredentialsPayload = Schema.Struct({
  headers: Schema.optional(Schema.Record(Schema.String, SecretBackedValue)),
  queryParams: Schema.optional(Schema.Record(Schema.String, SecretBackedValue)),
});

const OpenApiSecretShapePayload = Schema.Struct({
  kind: Schema.Literal("secret"),
  prefix: Schema.optional(Schema.String),
});

const OpenApiConfiguredValuePayload = Schema.Union([Schema.String, OpenApiSecretShapePayload]);

const SpecFetchCredentialsPayload = Schema.Struct({
  headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValuePayload)),
  queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValuePayload)),
});

// ---------------------------------------------------------------------------
// Payloads
// ---------------------------------------------------------------------------

const AddSpecPayload = Schema.Struct({
  spec: OpenApiSpecInputPayload,
  specFetchCredentials: Schema.optional(SpecFetchCredentialsPayload),
  name: Schema.String,
  baseUrl: Schema.String,
  namespace: Schema.String,
  headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValuePayload)),
  queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValuePayload)),
  oauth2: Schema.optional(OAuth2SourceConfig),
});

const PreviewSpecPayload = Schema.Struct({
  spec: Schema.String,
  specFetchCredentials: Schema.optional(PreviewSpecFetchCredentialsPayload),
});

const ConfigureCredentialPayload = Schema.Union([
  Schema.String,
  Schema.Struct({
    kind: Schema.Literal("text"),
    text: Schema.String,
    prefix: Schema.optional(Schema.String),
  }),
  Schema.Struct({
    kind: Schema.Literal("secret"),
    secretId: Schema.String,
    secretScope: Schema.optional(ScopeId),
    prefix: Schema.optional(Schema.String),
  }),
  Schema.Struct({
    kind: Schema.Literal("connection"),
    connectionId: ConnectionId,
  }),
]);

const ConfigureCredentialMapPayload = Schema.Record(Schema.String, ConfigureCredentialPayload);

const ConfigurePayload = Schema.Struct({
  source: Schema.Struct({
    id: Schema.String,
    scope: ScopeId,
  }),
  scope: ScopeId,
  headers: Schema.optional(ConfigureCredentialMapPayload),
  queryParams: Schema.optional(ConfigureCredentialMapPayload),
  specFetchCredentials: Schema.optional(
    Schema.Struct({
      headers: Schema.optional(ConfigureCredentialMapPayload),
      queryParams: Schema.optional(ConfigureCredentialMapPayload),
    }),
  ),
  oauth2: Schema.optional(
    Schema.Struct({
      clientId: Schema.optional(ConfigureCredentialPayload),
      clientSecret: Schema.optional(ConfigureCredentialPayload),
      connection: Schema.optional(ConfigureCredentialPayload),
    }),
  ),
  oauth2Source: Schema.optional(OAuth2SourceConfig),
});

// ---------------------------------------------------------------------------
// Responses
// ---------------------------------------------------------------------------

const AddSpecResponse = Schema.Struct({
  toolCount: Schema.Number,
  namespace: Schema.String,
});

// HTTP status on the three domain errors lives on their class
// declarations in `../sdk/errors.ts` — see the comment there.

// ---------------------------------------------------------------------------
// Group
//
// Plugin SDK errors (OpenApiParseError, OpenApiExtractionError,
// OpenApiOAuthError) are declared once at the group level via
// `.addError(...)` — every endpoint inherits them. The errors themselves
// carry their HTTP status via `HttpApiSchema.annotations` above, so
// handlers just `return yield* ext.foo(...)` and the schema encodes
// whatever comes out.
//
// 5xx is handled at the API level: `.addError(InternalError)` adds the
// shared opaque 500 surface. Defects are captured + downgraded to it by
// an HttpApiBuilder middleware (see apps/cloud/src/observability.ts).
// StorageError → InternalError translation happens at service wiring
// time via `withCapture(executor)`.
// ---------------------------------------------------------------------------

export const OpenApiGroup = HttpApiGroup.make("openapi")
  .add(
    HttpApiEndpoint.post("previewSpec", "/scopes/:scopeId/openapi/preview", {
      params: ScopeIdParam,
      payload: PreviewSpecPayload,
      success: SpecPreview,
      error: DomainErrors,
    }),
  )
  .add(
    HttpApiEndpoint.post("addSpec", "/scopes/:scopeId/openapi/specs", {
      params: ScopeIdParam,
      payload: AddSpecPayload,
      success: AddSpecResponse,
      error: DomainErrors,
    }),
  )
  .add(
    HttpApiEndpoint.get("getSource", "/scopes/:scopeId/openapi/sources/:namespace", {
      params: SourceParams,
      success: Schema.NullOr(StoredSourceSchema),
      error: DomainErrors,
    }),
  )
  .add(
    HttpApiEndpoint.post("configure", "/scopes/:scopeId/openapi/configure", {
      params: ScopeIdParam,
      payload: ConfigurePayload,
      success: Schema.Array(CredentialBindingRef),
      error: DomainErrors,
    }),
  );
