import "../server-only.js";
import type { StandardSchemaV1 } from "@standard-schema/spec";
import {
  BroadcastValidationError,
  broadcastLimits,
  type ChannelDefinition,
  channelKey,
  type InferChannelEvent,
  type InferChannelParams,
  parseChannelEvent,
  parseChannelParams,
} from "./index.js";

/** Server-authenticated scope captured at the originating HTTP boundary. JSON-safe for jobs. */
export interface BroadcastOrigin {
  readonly version: 1;
  readonly clientId: string;
  readonly scope: string;
}

const clientIdPattern = /^[a-zA-Z0-9_-]{22,128}$/;

function parseOrigin(value: unknown): BroadcastOrigin | undefined {
  if (!value || typeof value !== "object") return undefined;
  const candidate = value as Record<string, unknown>;
  if (
    candidate.version !== 1 ||
    typeof candidate.clientId !== "string" ||
    !clientIdPattern.test(candidate.clientId) ||
    typeof candidate.scope !== "string" ||
    candidate.scope.length === 0 ||
    candidate.scope.length > 2_048
  )
    return undefined;
  return { version: 1, clientId: candidate.clientId, scope: candidate.scope };
}

/** Validate an origin already captured by a trusted server for a job payload. */
export const broadcastOriginSchema: StandardSchemaV1<unknown, BroadcastOrigin> =
  {
    "~standard": {
      version: 1,
      vendor: "beignet",
      validate(value) {
        const origin = parseOrigin(value);
        return origin
          ? { value: origin }
          : { issues: [{ message: "Invalid broadcast origin" }] };
      },
    },
  };

/** Never derive principal, tenant, or namespace from the optional client header. */
export function resolveBroadcastOrigin(options: {
  headers: Headers;
  principalId?: string | null;
  tenantId?: string | null;
  namespace: string;
}): BroadcastOrigin | undefined {
  if (!options.principalId || !options.namespace) return undefined;
  return parseOrigin({
    version: 1,
    clientId: options.headers.get("X-Beignet-Broadcast-Client"),
    scope: JSON.stringify([
      options.namespace,
      options.tenantId ?? null,
      options.principalId,
    ]),
  });
}

export type BroadcastPublication<C extends ChannelDefinition> =
  InferChannelEvent<C> & {
    params: InferChannelParams<C>;
    excludeOrigin?: BroadcastOrigin;
  };

/** Readiness proves initial subscription only. Continuity loss requires reconnect and refetch. */
export interface BroadcastSubscription {
  readonly ready: Promise<void>;
  unsubscribe(): Promise<void>;
}

export interface BroadcastPort {
  /** Resolves when accepted by the provider, including when nobody is subscribed. */
  publish<C extends ChannelDefinition>(
    channel: C,
    publication: BroadcastPublication<C>,
  ): Promise<void>;
  subscribe<C extends ChannelDefinition>(
    channel: C,
    options: {
      params: InferChannelParams<C>;
      onEvent: (
        event: InferChannelEvent<C>,
        metadata: { excludeOrigin?: BroadcastOrigin },
      ) => void;
      onDisconnect: () => void;
    },
  ): BroadcastSubscription;
}

/** Internal transport envelope carried by broadcast providers, never a browser payload. */
export interface BroadcastEnvelope {
  version: 1;
  channel: string;
  params: Record<string, string>;
  event: string;
  data: unknown;
  excludeOrigin?: BroadcastOrigin;
}

/** Low-level adapter contract. Subscription callbacks must be isolated from publishers. */
export interface BroadcastTransport {
  publish(key: string, envelope: BroadcastEnvelope): Promise<void>;
  subscribe(
    key: string,
    options: {
      onMessage: (envelope: unknown) => void;
      onDisconnect: () => void;
    },
  ): BroadcastSubscription;
}

/** Shared validation and cleanup for provider implementations. */
export function createBroadcastPort(
  transport: BroadcastTransport,
): BroadcastPort {
  return {
    async publish(channel, publication) {
      const params = await parseChannelParams(channel, publication.params);
      const event = await parseChannelEvent(channel, publication);
      const excludeOrigin =
        publication.excludeOrigin === undefined
          ? undefined
          : parseOrigin(publication.excludeOrigin);
      if (publication.excludeOrigin !== undefined && !excludeOrigin)
        throw new BroadcastValidationError("Invalid broadcast origin");
      await transport.publish(channelKey(channel.name, params), {
        version: 1,
        channel: channel.name,
        params,
        ...event,
        ...(excludeOrigin ? { excludeOrigin } : {}),
      });
    },
    subscribe(channel, options) {
      let stopped = false;
      let failed = false;
      let subscription: BroadcastSubscription | undefined;
      let queued = 0;
      let queuedBytes = 0;
      let pending = Promise.resolve();
      let cancel: (() => void) | undefined;
      let cleanup: Promise<void> | undefined;
      const release = () =>
        (cleanup ??= subscription?.unsubscribe() ?? Promise.resolve());
      const disconnect = () => {
        if (stopped || failed) return;
        failed = true;
        try {
          options.onDisconnect();
        } catch {
          /* Consumer callbacks cannot break provider lifecycle. */
        }
      };
      const initialize = (async () => {
        const params = await parseChannelParams(channel, options.params);
        if (stopped) return;
        const key = channelKey(channel.name, params);
        subscription = transport.subscribe(key, {
          onDisconnect: disconnect,
          onMessage(value) {
            if (stopped || failed) return;
            let bytes: number;
            try {
              bytes = new TextEncoder().encode(
                JSON.stringify(value),
              ).byteLength;
            } catch {
              disconnect();
              return;
            }
            if (
              bytes >
                broadcastLimits.payloadBytes +
                  broadcastLimits.requestBytes +
                  16_384 ||
              queuedBytes + bytes > broadcastLimits.bufferedBytes
            ) {
              disconnect();
              return;
            }
            if (++queued > 128) {
              disconnect();
              return;
            }
            queuedBytes += bytes;
            pending = pending
              .then(async () => {
                if (stopped || failed) return;
                if (!value || typeof value !== "object")
                  throw new BroadcastValidationError(
                    "Invalid broadcast envelope",
                  );
                const envelope = value as BroadcastEnvelope;
                if (envelope.version !== 1 || envelope.channel !== channel.name)
                  throw new BroadcastValidationError(
                    "Invalid broadcast envelope",
                  );
                const receivedParams = await parseChannelParams(
                  channel,
                  envelope.params,
                );
                if (channelKey(channel.name, receivedParams) !== key)
                  throw new BroadcastValidationError(
                    "Broadcast channel mismatch",
                  );
                const event = await parseChannelEvent(channel, envelope);
                const excludeOrigin =
                  envelope.excludeOrigin === undefined
                    ? undefined
                    : parseOrigin(envelope.excludeOrigin);
                if (envelope.excludeOrigin !== undefined && !excludeOrigin)
                  throw new BroadcastValidationError(
                    "Invalid broadcast origin",
                  );
                if (!stopped && !failed)
                  await options.onEvent(event, { excludeOrigin });
              })
              .catch(disconnect)
              .finally(() => {
                queued--;
                queuedBytes -= bytes;
              });
          },
        });
        await subscription.ready;
        if (stopped) await release();
        if (failed)
          throw new Error(
            "Broadcast subscription lost continuity before readiness",
          );
      })();
      let timeout: ReturnType<typeof setTimeout> | undefined;
      const ready = Promise.race([
        initialize,
        new Promise<never>((_, reject) => {
          cancel = () => reject(new Error("Broadcast subscription cancelled"));
          timeout = setTimeout(
            () =>
              reject(new Error("Broadcast subscription readiness timed out")),
            broadcastLimits.readyTimeoutMs,
          );
        }),
      ])
        .catch((error) => {
          stopped = true;
          void release().catch(() => undefined);
          throw error;
        })
        .finally(() => {
          if (timeout !== undefined) clearTimeout(timeout);
          cancel = undefined;
        });
      // Callers still observe rejection through ready; early cancellation must not leak a rejection.
      void ready.catch(() => undefined);
      return {
        ready,
        async unsubscribe() {
          stopped = true;
          cancel?.();
          await ready.catch(() => undefined);
          await release();
        },
      };
    },
  };
}

export interface ChannelBinding<Ctx> {
  readonly kind: "channel-binding";
  readonly channel: ChannelDefinition;
  authorize: (args: {
    ctx: Ctx;
    params: Record<string, string>;
  }) => void | Promise<void>;
}

export interface ChannelRegistry<Ctx> {
  readonly kind: "channel-registry";
  readonly bindings: readonly ChannelBinding<Ctx>[];
  get(name: string): ChannelBinding<Ctx> | undefined;
}

/** Bind every channel explicitly, including public channels. */
export function createBroadcasting<Ctx>() {
  function defineChannelBinding<C extends ChannelDefinition>(
    channel: C,
    options: {
      authorize: (args: {
        ctx: Ctx;
        params: InferChannelParams<C>;
      }) => void | Promise<void>;
    },
  ): ChannelBinding<Ctx> {
    return Object.freeze({
      kind: "channel-binding" as const,
      channel,
      authorize: ({
        ctx,
        params,
      }: {
        ctx: Ctx;
        params: Record<string, string>;
      }) => options.authorize({ ctx, params: params as InferChannelParams<C> }),
    });
  }
  function defineChannelRegistry(
    bindings: readonly ChannelBinding<Ctx>[],
  ): ChannelRegistry<Ctx> {
    const byName = new Map<string, ChannelBinding<Ctx>>();
    for (const binding of bindings) {
      if (byName.has(binding.channel.name))
        throw new BroadcastValidationError(
          `Duplicate channel ${binding.channel.name}`,
        );
      byName.set(binding.channel.name, binding);
    }
    return Object.freeze({
      kind: "channel-registry",
      bindings: Object.freeze([...bindings]),
      get: (name: string) => byName.get(name),
    });
  }
  return { defineChannelBinding, defineChannelRegistry };
}

/** Validate a configurable deadline without allowing an unbounded stream. */
export function broadcastLifetime(
  value: number = broadcastLimits.defaultLifetimeMs,
): number {
  if (
    !Number.isSafeInteger(value) ||
    value < 1 ||
    value > broadcastLimits.maxLifetimeMs
  )
    throw new BroadcastValidationError(
      `Broadcast maxLifetimeMs must be a safe integer between 1 and ${broadcastLimits.maxLifetimeMs} milliseconds`,
    );
  return value;
}
