import type { HermesLegacySession, HermesRuntimeTransport } from '@mosaicstack/agent';
import type {
  RuntimeAttachHandle,
  RuntimeAttachMode,
  RuntimeMessage,
  RuntimeScope,
  RuntimeStreamEvent,
} from '@mosaicstack/types';

/** Concrete HTTP transport for a configured legacy Hermes runtime endpoint. */
export class GatewayHermesRuntimeTransport implements HermesRuntimeTransport {
  constructor(
    private readonly baseUrl = process.env['MOSAIC_HERMES_RUNTIME_URL']?.trim(),
    private readonly serviceToken = process.env['MOSAIC_HERMES_RUNTIME_TOKEN']?.trim(),
    private readonly fetchFn: typeof fetch = fetch,
  ) {}

  async capabilities(scope: RuntimeScope): Promise<string[]> {
    return this.request<string[]>('/capabilities', scope);
  }

  async health(scope: RuntimeScope): Promise<{ status: string; detail?: string }> {
    return this.request<{ status: string; detail?: string }>('/health', scope);
  }

  async sessions(scope: RuntimeScope): Promise<HermesLegacySession[]> {
    return this.request<HermesLegacySession[]>('/sessions', scope);
  }

  async *stream(
    sessionId: string,
    cursor: string | undefined,
    scope: RuntimeScope,
  ): AsyncIterable<RuntimeStreamEvent> {
    const params = new URLSearchParams(cursor ? { cursor } : {});
    const events = await this.request<RuntimeStreamEvent[]>(
      `/sessions/${encodeURIComponent(sessionId)}/stream?${params.toString()}`,
      scope,
    );
    yield* events;
  }

  async send(sessionId: string, message: RuntimeMessage, scope: RuntimeScope): Promise<void> {
    await this.request(`/sessions/${encodeURIComponent(sessionId)}/messages`, scope, {
      method: 'POST',
      body: message,
    });
  }

  async attach(
    sessionId: string,
    mode: RuntimeAttachMode,
    scope: RuntimeScope,
  ): Promise<RuntimeAttachHandle> {
    return this.request<RuntimeAttachHandle>(
      `/sessions/${encodeURIComponent(sessionId)}/attach`,
      scope,
      {
        method: 'POST',
        body: { mode },
      },
    );
  }

  async detach(attachmentId: string, scope: RuntimeScope): Promise<void> {
    await this.request(`/attachments/${encodeURIComponent(attachmentId)}`, scope, {
      method: 'DELETE',
    });
  }

  async terminate(sessionId: string, approvalRef: string, scope: RuntimeScope): Promise<void> {
    await this.request(`/sessions/${encodeURIComponent(sessionId)}/terminate`, scope, {
      method: 'POST',
      body: { approvalRef },
    });
  }

  private async request<T>(
    path: string,
    scope: RuntimeScope,
    init: { method?: string; body?: unknown } = {},
  ): Promise<T> {
    if (!this.baseUrl || !this.serviceToken) {
      throw new Error(
        'MOSAIC_HERMES_RUNTIME_URL and MOSAIC_HERMES_RUNTIME_TOKEN must configure Hermes transport',
      );
    }
    const endpoint = new URL(this.baseUrl);
    if (endpoint.protocol !== 'https:' && !isLoopbackHttp(endpoint)) {
      throw new Error('Hermes runtime transport requires HTTPS outside loopback');
    }
    const response = await this.fetchFn(
      new URL(path.replace(/^\//, ''), `${endpoint.toString().replace(/\/$/, '')}/`),
      {
        method: init.method ?? 'GET',
        headers: {
          accept: 'application/json',
          authorization: `Bearer ${this.serviceToken}`,
          'x-mosaic-actor-id': scope.actorId,
          'x-mosaic-tenant-id': scope.tenantId,
          'x-mosaic-channel-id': scope.channelId,
          'x-correlation-id': scope.correlationId,
          ...(init.body ? { 'content-type': 'application/json' } : {}),
        },
        ...(init.body ? { body: JSON.stringify(init.body) } : {}),
      },
    );
    if (!response.ok) throw new Error(`Hermes runtime request failed: ${response.status}`);
    if (response.status === 204) return undefined as T;
    return (await response.json()) as T;
  }
}

function isLoopbackHttp(endpoint: URL): boolean {
  return (
    endpoint.protocol === 'http:' &&
    (endpoint.hostname === 'localhost' ||
      endpoint.hostname === '127.0.0.1' ||
      endpoint.hostname === '::1')
  );
}
