// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
//
// WU-2732 (INIT-060 Phase 3, ADR-013 §1 abort + §6 governance):
// Pattern A tracer-bullet mock implementation for the six
// remote-control tools. Returns deterministic fixed responses so the
// wire contract (manifest entries, POST /tools/:name round-trip,
// tool_called emission) can be proven BEFORE the real kernel-side
// implementation lands in WU-2733.
//
// REAL IMPL WILL REPLACE THIS FILE: WU-2733 ships
// tool-impl/remote-controls.ts with the real pause/resume/abort logic.
// The mock's role is purely contract stabilisation.

import { randomUUID } from 'node:crypto';
import type { ExecutionContext, ToolOutput } from '@lumenflow/kernel';
import {
  AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES,
  MOCK_SESSION_ID,
  type AbortTurnInvocationResult,
  type AgentRuntimeRemoteControlToolName,
  type AnyRemoteControlInvocationResult,
  type RemoteControlInvocationResult,
} from '../remote-controls/types.js';
import type {
  AbortTurnInvocationInput,
  RemoteControlInvocationInput,
  RemoteControlPort,
} from '../remote-controls/port.js';
import {
  buildToolCalledEvent,
  emitAgentRuntimeEvent,
  type AgentRuntimeEventSink,
} from '../turn-lifecycle-events.js';

const MOCK_TURN_INDEX = 0;
const MOCK_CLEANUP_STATUS_CLEAN = 'clean' as const;
const MOCK_ABORT_RECOVERY_ACTION: string | null = null;

/**
 * Mock adapter implementing RemoteControlPort. Every invocation:
 *  1. Returns a deterministic response matching the declared contract.
 *  2. Emits `agent-runtime:tool_called` via the supplied sink (ADR-013
 *     §6 governance — every tool call is auditable).
 *
 * Fixed session_id + turn_index in the emitted event mean tests can
 * assert exact payloads without runtime coupling.
 */
export class MockRemoteControlAdapter implements RemoteControlPort {
  constructor(private readonly eventSink?: AgentRuntimeEventSink) {}

  async resumeWorkflow(
    input: RemoteControlInvocationInput,
  ): Promise<RemoteControlInvocationResult> {
    return this.handle(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.RESUME_WORKFLOW, input);
  }

  async pauseTurn(input: RemoteControlInvocationInput): Promise<RemoteControlInvocationResult> {
    return this.handle(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.PAUSE_TURN, input);
  }

  async abortTurn(input: AbortTurnInvocationInput): Promise<AbortTurnInvocationResult> {
    const sessionId = resolveSessionId(input.session_id);
    this.emitToolCalled(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ABORT_TURN, sessionId);
    return {
      status: 'ok',
      tool_name: AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ABORT_TURN,
      session_id: sessionId,
      cleanup_status: MOCK_CLEANUP_STATUS_CLEAN,
      recovery_action: MOCK_ABORT_RECOVERY_ACTION,
    };
  }

  async elevateAutonomy(
    input: RemoteControlInvocationInput,
  ): Promise<RemoteControlInvocationResult> {
    return this.handle(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ELEVATE_AUTONOMY, input);
  }

  async lowerAutonomy(input: RemoteControlInvocationInput): Promise<RemoteControlInvocationResult> {
    return this.handle(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.LOWER_AUTONOMY, input);
  }

  async approveInflight(
    input: RemoteControlInvocationInput,
  ): Promise<RemoteControlInvocationResult> {
    return this.handle(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.APPROVE_INFLIGHT, input);
  }

  private handle(
    toolName: AgentRuntimeRemoteControlToolName,
    input: RemoteControlInvocationInput,
  ): RemoteControlInvocationResult {
    const sessionId = resolveSessionId(input.session_id);
    this.emitToolCalled(toolName, sessionId);
    return {
      status: 'ok',
      tool_name: toolName,
      session_id: sessionId,
    };
  }

  private emitToolCalled(toolName: AgentRuntimeRemoteControlToolName, sessionId: string): void {
    if (!this.eventSink) {
      return;
    }
    emitAgentRuntimeEvent(
      this.eventSink,
      buildToolCalledEvent({
        session_id: sessionId,
        turn_index: MOCK_TURN_INDEX,
        tool_name: toolName,
        tool_call_id: randomUUID(),
      }),
    );
  }
}

function resolveSessionId(input: string | undefined): string {
  if (input !== undefined && input.trim().length > 0) {
    return input;
  }
  return MOCK_SESSION_ID;
}

/**
 * Factory for the default mock adapter. Tests and the manifest-wired
 * tool functions below both use this so the fixture surface has a
 * single instantiation path.
 */
export function createMockRemoteControlAdapter(
  eventSink?: AgentRuntimeEventSink,
): MockRemoteControlAdapter {
  return new MockRemoteControlAdapter(eventSink);
}

/**
 * Manifest-entrypoint adapter: each of the six exported functions
 * matches the kernel's InProcessToolFn signature
 * `(input: unknown, ctx: ExecutionContext) => Promise<ToolOutput>`.
 *
 * The module-scoped sink is rebound via `setMockRemoteControlEventSink`
 * for tests that want to assert tool_called emission. Production
 * wiring (HTTP surface → runtime → manifest-dispatch) will ignore the
 * sink and rely on runtime-level tool_called events; the in-process
 * sink is a mock-only convenience for the unit suite.
 */
let moduleEventSink: AgentRuntimeEventSink | undefined;

export function setMockRemoteControlEventSink(sink: AgentRuntimeEventSink | undefined): void {
  moduleEventSink = sink;
}

function buildToolOutput(data: AnyRemoteControlInvocationResult): ToolOutput {
  return {
    success: true,
    data,
    metadata: {
      mock: true,
      tracer_bullet: 'WU-2732',
    },
  };
}

async function invokeMock(
  toolName: AgentRuntimeRemoteControlToolName,
  input: unknown,
): Promise<ToolOutput> {
  const adapter = createMockRemoteControlAdapter(moduleEventSink);
  const parsed = parseInput(input);

  switch (toolName) {
    case AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.RESUME_WORKFLOW:
      return buildToolOutput(await adapter.resumeWorkflow(parsed));
    case AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.PAUSE_TURN:
      return buildToolOutput(await adapter.pauseTurn(parsed));
    case AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ABORT_TURN:
      return buildToolOutput(await adapter.abortTurn(parsed));
    case AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ELEVATE_AUTONOMY:
      return buildToolOutput(await adapter.elevateAutonomy(parsed));
    case AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.LOWER_AUTONOMY:
      return buildToolOutput(await adapter.lowerAutonomy(parsed));
    case AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.APPROVE_INFLIGHT:
      return buildToolOutput(await adapter.approveInflight(parsed));
    default:
      return exhaustive(toolName);
  }
}

function parseInput(input: unknown): AbortTurnInvocationInput {
  if (input === null || input === undefined) {
    return {};
  }
  if (typeof input !== 'object') {
    return {};
  }
  const record = input as Record<string, unknown>;
  const result: AbortTurnInvocationInput = {};
  if (typeof record.session_id === 'string') {
    result.session_id = record.session_id;
  }
  if (typeof record.reason === 'string') {
    result.reason = record.reason;
  }
  return result;
}

function exhaustive(value: never): never {
  throw new Error(`Unhandled remote-control tool: ${String(value)}`);
}

export async function resumeWorkflowMockTool(
  input: unknown,
  _ctx: ExecutionContext,
): Promise<ToolOutput> {
  return invokeMock(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.RESUME_WORKFLOW, input);
}

export async function pauseTurnMockTool(
  input: unknown,
  _ctx: ExecutionContext,
): Promise<ToolOutput> {
  return invokeMock(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.PAUSE_TURN, input);
}

export async function abortTurnMockTool(
  input: unknown,
  _ctx: ExecutionContext,
): Promise<ToolOutput> {
  return invokeMock(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ABORT_TURN, input);
}

export async function elevateAutonomyMockTool(
  input: unknown,
  _ctx: ExecutionContext,
): Promise<ToolOutput> {
  return invokeMock(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.ELEVATE_AUTONOMY, input);
}

export async function lowerAutonomyMockTool(
  input: unknown,
  _ctx: ExecutionContext,
): Promise<ToolOutput> {
  return invokeMock(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.LOWER_AUTONOMY, input);
}

export async function approveInflightMockTool(
  input: unknown,
  _ctx: ExecutionContext,
): Promise<ToolOutput> {
  return invokeMock(AGENT_RUNTIME_REMOTE_CONTROL_TOOL_NAMES.APPROVE_INFLIGHT, input);
}
