import {
  AuthBearerBootstrapResult,
  AuthSessionState,
  AuthWebSocketTokenResult,
  type AuthBearerBootstrapResult as AuthBearerBootstrapResultType,
  type AuthSessionState as AuthSessionStateType,
  type AuthWebSocketTokenResult as AuthWebSocketTokenResultType,
  ExecutionEnvironmentDescriptor,
  type ExecutionEnvironmentDescriptor as ExecutionEnvironmentDescriptorType,
} from "@belweave/contracts";
import { SshHttpBridgeError } from "@belweave/ssh/errors";
import { fetchLoopbackSshJson } from "@belweave/ssh/tunnel";
import * as Context from "effect/Context";
import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import { HttpClient } from "effect/unstable/http";

export type DesktopSshRemoteApiOperation =
  | "fetch-environment-descriptor"
  | "bootstrap-bearer-session"
  | "fetch-session-state"
  | "issue-websocket-token";

export class DesktopSshRemoteApiError extends Data.TaggedError("DesktopSshRemoteApiError")<{
  readonly operation: DesktopSshRemoteApiOperation;
  readonly cause: SshHttpBridgeError | Schema.SchemaError;
}> {
  override get message() {
    return `SSH remote API request failed during ${this.operation}.`;
  }
}

export interface DesktopSshRemoteApiShape {
  readonly fetchEnvironmentDescriptor: (input: {
    readonly httpBaseUrl: string;
  }) => Effect.Effect<ExecutionEnvironmentDescriptorType, DesktopSshRemoteApiError>;
  readonly bootstrapBearerSession: (input: {
    readonly httpBaseUrl: string;
    readonly credential: string;
  }) => Effect.Effect<AuthBearerBootstrapResultType, DesktopSshRemoteApiError>;
  readonly fetchSessionState: (input: {
    readonly httpBaseUrl: string;
    readonly bearerToken: string;
  }) => Effect.Effect<AuthSessionStateType, DesktopSshRemoteApiError>;
  readonly issueWebSocketToken: (input: {
    readonly httpBaseUrl: string;
    readonly bearerToken: string;
  }) => Effect.Effect<AuthWebSocketTokenResultType, DesktopSshRemoteApiError>;
}

export class DesktopSshRemoteApi extends Context.Service<
  DesktopSshRemoteApi,
  DesktopSshRemoteApiShape
>()("belweave/desktop/SshRemoteApi") {}

const decodeExecutionEnvironmentDescriptor = Schema.decodeUnknownEffect(
  ExecutionEnvironmentDescriptor,
);
const decodeAuthBearerBootstrapResult = Schema.decodeUnknownEffect(AuthBearerBootstrapResult);
const decodeAuthSessionState = Schema.decodeUnknownEffect(AuthSessionState);
const decodeAuthWebSocketTokenResult = Schema.decodeUnknownEffect(AuthWebSocketTokenResult);

const mapError =
  (operation: DesktopSshRemoteApiOperation) =>
  (cause: SshHttpBridgeError | Schema.SchemaError): DesktopSshRemoteApiError =>
    new DesktopSshRemoteApiError({ operation, cause });

const make = Effect.gen(function* () {
  const httpClient = yield* HttpClient.HttpClient;
  const provideHttpClient = <A, E>(effect: Effect.Effect<A, E, HttpClient.HttpClient>) =>
    effect.pipe(Effect.provideService(HttpClient.HttpClient, httpClient));

  return DesktopSshRemoteApi.of({
    fetchEnvironmentDescriptor: ({ httpBaseUrl }) =>
      fetchLoopbackSshJson<unknown>({
        httpBaseUrl,
        pathname: "/.well-known/belweave/environment",
      }).pipe(
        Effect.flatMap(decodeExecutionEnvironmentDescriptor),
        Effect.mapError(mapError("fetch-environment-descriptor")),
        provideHttpClient,
        Effect.withSpan("desktop.sshRemoteApi.fetchEnvironmentDescriptor"),
      ),
    bootstrapBearerSession: ({ httpBaseUrl, credential }) =>
      fetchLoopbackSshJson<unknown>({
        httpBaseUrl,
        pathname: "/api/auth/bootstrap/bearer",
        method: "POST",
        body: { credential },
      }).pipe(
        Effect.flatMap(decodeAuthBearerBootstrapResult),
        Effect.mapError(mapError("bootstrap-bearer-session")),
        provideHttpClient,
        Effect.withSpan("desktop.sshRemoteApi.bootstrapBearerSession"),
      ),
    fetchSessionState: ({ httpBaseUrl, bearerToken }) =>
      fetchLoopbackSshJson<unknown>({
        httpBaseUrl,
        pathname: "/api/auth/session",
        bearerToken,
      }).pipe(
        Effect.flatMap(decodeAuthSessionState),
        Effect.mapError(mapError("fetch-session-state")),
        provideHttpClient,
        Effect.withSpan("desktop.sshRemoteApi.fetchSessionState"),
      ),
    issueWebSocketToken: ({ httpBaseUrl, bearerToken }) =>
      fetchLoopbackSshJson<unknown>({
        httpBaseUrl,
        pathname: "/api/auth/ws-token",
        method: "POST",
        bearerToken,
      }).pipe(
        Effect.flatMap(decodeAuthWebSocketTokenResult),
        Effect.mapError(mapError("issue-websocket-token")),
        provideHttpClient,
        Effect.withSpan("desktop.sshRemoteApi.issueWebSocketToken"),
      ),
  });
});

export const layer = Layer.effect(DesktopSshRemoteApi, make);
