/**
 * LegacySdkHostAdapter — wraps existing AgentSdkAdapter as AgentHostAdapter (WP-03).
 * Compatibility shim only; does not replace production SessionManager wiring.
 */

import { randomUUID } from "node:crypto";

import type {
  AgentCreateSpec,
  AgentSdkAdapter,
  AgentSendSpec,
} from "../../registry/AgentSdkAdapter.ts";
import type { RunHandle, RuntimeEvent } from "../../types/state.ts";
import type { AgentHostAdapter } from "../contracts/AgentHostAdapter.ts";
import type { HostDescriptor } from "../contracts/HostDescriptor.ts";
import type { HostEventEnvelope } from "../contracts/HostEvent.ts";
import { toHostEventEnvelope } from "../contracts/HostEvent.ts";
import type { HostFailure } from "../contracts/HostFailure.ts";
import type { HostRunRef, StartRunInput } from "../contracts/HostRun.ts";
import type {
  HostSessionRef,
  HostSessionState,
  PrepareSessionInput,
  PreparedHostSession,
} from "../contracts/HostSession.ts";
import { LEGACY_SDK_HOST_CAPABILITIES } from "./capabilityPresets.ts";

export interface LegacySdkHostAdapterOptions {
  hostId?: string;
  displayName?: string;
  enabled?: boolean;
  /** Default layer / runtime for AgentSdkAdapter.create when preparing a new session. */
  defaultRole?: string;
  defaultLayer?: AgentCreateSpec["layer"];
  defaultRuntime?: AgentCreateSpec["runtime"];
}

type ActiveRun = {
  ref: HostRunRef;
  handle: RunHandle;
  input: StartRunInput;
};

/**
 * Maps AgentSdkAdapter (create/list/resume/send) onto AgentHostAdapter.
 * Vendor-specific fields stay inside the wrapped adapter; HostDescriptor
 * remains platform-agnostic (adapterType = "legacy-sdk").
 */
export class LegacySdkHostAdapter implements AgentHostAdapter {
  readonly hostId: string;
  readonly #sdk: AgentSdkAdapter;
  readonly #displayName: string;
  readonly #enabled: boolean;
  readonly #defaultRole: string;
  readonly #defaultLayer: AgentCreateSpec["layer"];
  readonly #defaultRuntime: AgentCreateSpec["runtime"];
  readonly #sessionState = new Map<string, HostSessionState>();
  readonly #activeRuns = new Map<string, ActiveRun>();

  constructor(sdk: AgentSdkAdapter, opts: LegacySdkHostAdapterOptions = {}) {
    this.#sdk = sdk;
    this.hostId = opts.hostId?.trim() || "host-legacy-sdk";
    this.#displayName = opts.displayName?.trim() || "Legacy SDK Host";
    this.#enabled = opts.enabled !== false;
    this.#defaultRole = opts.defaultRole?.trim() || "DEV";
    this.#defaultLayer = opts.defaultLayer ?? "worker";
    this.#defaultRuntime = opts.defaultRuntime ?? "local";
  }

  /** Expose wrapped adapter for compatibility assertions (tests / diagnostics). */
  get underlyingSdkAdapter(): AgentSdkAdapter {
    return this.#sdk;
  }

  async describe(): Promise<HostDescriptor> {
    return {
      hostId: this.hostId,
      adapterType: "legacy-sdk",
      displayName: this.#displayName,
      enabled: this.#enabled,
      capabilities: { ...LEGACY_SDK_HOST_CAPABILITIES },
      version: "wp-03",
      metadata: { mode: "off", wraps: "AgentSdkAdapter" },
    };
  }

  async prepareSession(input: PrepareSessionInput): Promise<PreparedHostSession> {
    if (input.existing?.externalSessionId) {
      try {
        await this.#sdk.resume(input.existing.externalSessionId);
        this.#sessionState.set(input.existing.externalSessionId, "ready");
        return {
          reference: { ...input.existing, hostId: this.hostId },
          state: "ready",
          created: false,
        };
      } catch {
        // fall through to create
      }
    }

    const created = await this.#sdk.create({
      agentId: input.worker.workerId,
      role: input.worker.roleId || this.#defaultRole,
      layer: input.worker.layer === "leader" ? "leader" : this.#defaultLayer,
      runtime: this.#defaultRuntime,
      workspace: input.workspace,
      modelId: input.modelHint,
    });
    const reference: HostSessionRef = {
      hostId: this.hostId,
      externalSessionId: created.sdk_agent_id,
      mode: "local",
      metadata: {
        workerId: input.worker.workerId,
        roleId: input.worker.roleId,
      },
    };
    this.#sessionState.set(reference.externalSessionId, "ready");
    return { reference, state: "ready", created: true };
  }

  async inspectSession(reference: HostSessionRef): Promise<HostSessionState> {
    return this.#sessionState.get(reference.externalSessionId) ?? "unknown";
  }

  async startRun(session: HostSessionRef, input: StartRunInput): Promise<HostRunRef> {
    const sendSpec: AgentSendSpec = {
      sessionId: `session-${input.runId}`,
      agentId: input.workerId,
      text: input.prompt,
      modelId: input.modelHint,
    };
    const handle = await this.#sdk.send(sendSpec, session.externalSessionId);
    const ref: HostRunRef = {
      hostId: this.hostId,
      externalRunId: handle.run_id,
      session,
    };
    this.#activeRuns.set(handle.run_id, { ref, handle, input });
    this.#sessionState.set(session.externalSessionId, "running");
    return ref;
  }

  async *streamEvents(run: HostRunRef): AsyncIterable<HostEventEnvelope> {
    const key = run.externalRunId ?? "";
    const active = this.#activeRuns.get(key);
    if (!active) {
      yield toHostEventEnvelope({
        eventId: `evt-${randomUUID()}`,
        at: new Date().toISOString(),
        hostId: this.hostId,
        workerId: "unknown",
        runId: key || "unknown",
        type: "host.event.opaque",
        raw: { reason: "legacy_run_not_found" },
      });
      return;
    }

    const queue: HostEventEnvelope[] = [];
    let settled = false;
    const unsub = active.handle.onEvent((event: RuntimeEvent) => {
      queue.push(
        toHostEventEnvelope({
          eventId: `evt-${randomUUID()}`,
          at: new Date().toISOString(),
          hostId: this.hostId,
          workerId: active.input.workerId,
          runId: active.input.runId,
          type: event.event_type || "host.event.opaque",
          payload: event.payload ?? event,
          raw: event,
        }),
      );
    });

    const settlePromise = active.handle.whenSettled().finally(() => {
      settled = true;
      unsub();
      this.#sessionState.set(run.session.externalSessionId, "idle");
    });

    while (!settled || queue.length > 0) {
      if (queue.length > 0) {
        yield queue.shift()!;
        continue;
      }
      await Promise.race([
        settlePromise,
        new Promise((r) => setTimeout(r, 5)),
      ]);
    }
  }

  async cancelRun(run: HostRunRef, reason: string): Promise<void> {
    const active = this.#activeRuns.get(run.externalRunId ?? "");
    if (active) await active.handle.cancel(reason);
  }

  classifyError(error: unknown): HostFailure {
    const message = error instanceof Error ? error.message : String(error);
    const lower = message.toLowerCase();
    if (lower.includes("rate") || lower.includes("429")) {
      return { kind: "rate_limited", message, retryable: true, raw: error };
    }
    if (lower.includes("busy")) {
      return { kind: "session_busy", message, retryable: true, raw: error };
    }
    if (lower.includes("not found") || lower.includes("lost")) {
      return { kind: "session_lost", message, retryable: false, raw: error };
    }
    if (lower.includes("permission") || lower.includes("forbidden")) {
      return { kind: "permission", message, retryable: false, raw: error };
    }
    return { kind: "unknown", message, retryable: false, raw: error };
  }
}
