import { BestAgentError } from "@best-agent/foundation";
import type { SandboxProfile, Turn } from "@best-agent/protocol";
import type { AgentProfile } from "../../agent/profile-registry.js";
import type { PreparedToolInvocation, RuntimeToolDefinition } from "../../tool/registry.js";
import type { ToolResumeContext } from "../checkpoint-codec.js";
import type { ExecuteToolInvocationOptions } from "../agent-runtime-tool-execution-types.js";
import type { ToolExecutionRuntime } from "./tool-execution-types.js";
import type { PersistTerminalToolFailure, ToolShellDebugBase } from "./tool-execution-types.js";

export async function authorizePreparedToolExecution(input: {
  authorizationTurn: Turn;
  options: ExecuteToolInvocationOptions;
  persistTerminalToolFailure: PersistTerminalToolFailure;
  prepared: PreparedToolInvocation;
  preparedToolResumeContext: ToolResumeContext;
  profile: AgentProfile;
  runtime: ToolExecutionRuntime;
  shellDebugBase?: ToolShellDebugBase;
  toolCallId: ToolResumeContext["toolCallId"];
  toolDefinition: RuntimeToolDefinition;
  toolName: string;
  turn: Turn;
}): Promise<{ executionSandboxProfile?: SandboxProfile; failureOutput?: string }> {
  const {
    authorizationTurn,
    options,
    persistTerminalToolFailure,
    prepared,
    preparedToolResumeContext,
    profile,
    runtime,
    shellDebugBase,
    toolCallId,
    toolDefinition,
    toolName,
    turn,
  } = input;
  const toolCapabilityRequest =
    toolDefinition.source === "plugin"
      ? { group: "tool" as const, kind: "plugin-hook" as const, toolName }
      : { group: "tool" as const, toolName };
  if (shellDebugBase) {
    runtime.tracing.logShellToolDebug(runtime.cwd, "shell.authorization.tool.start", {
      ...shellDebugBase,
      callId: toolCallId,
      request: runtime.tracing.describeCapabilityRequestForDebugLog(toolCapabilityRequest),
    });
  }
  const toolAuthorization = await runtime.approvals.authorizeCapability(
    authorizationTurn,
    profile,
    toolCapabilityRequest,
    toolCallId,
    options.resumeCheckpoint?.kind === "approval" ? options.resumeCheckpoint : undefined,
    {
      ...preparedToolResumeContext,
      toolCallId,
    },
    turn,
  );
  if (shellDebugBase) {
    runtime.tracing.logShellToolDebug(runtime.cwd, "shell.authorization.tool.done", {
      ...shellDebugBase,
      callId: toolCallId,
      result: runtime.tracing.describeAuthorizationResultForDebugLog(toolAuthorization),
    });
  }
  if (toolAuthorization !== "allow") {
    const termination = runtime.interruptions.createTurnTerminatedError(toolAuthorization);
    await persistTerminalToolFailure(toolCallId, prepared.normalizedInput, undefined, termination);
    throw termination;
  }

  let executionSandboxProfile: SandboxProfile | undefined;
  for (const request of prepared.capabilityRequests) {
    const normalizedRequest =
      toolDefinition.source === "plugin" && !request.kind
        ? { ...request, kind: "plugin-hook" as const }
        : request;
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.authorization.capability.start", {
        ...shellDebugBase,
        callId: toolCallId,
        request: runtime.tracing.describeCapabilityRequestForDebugLog(normalizedRequest),
      });
    }
    const deterministicDenial = runtime.capability.getDeterministicCapabilityDenial(
      profile,
      normalizedRequest,
    );
    if (deterministicDenial) {
      const failureOutput = await persistTerminalToolFailure(
        toolCallId,
        prepared.normalizedInput,
        {
          ...preparedToolResumeContext,
          toolCallId,
        },
        new BestAgentError("TOOL_CAPABILITY_DENIED", deterministicDenial),
      );
      if (failureOutput !== undefined) {
        return { failureOutput };
      }
      throw new BestAgentError("TOOL_CAPABILITY_DENIED", deterministicDenial);
    }
    const authorization = await runtime.approvals.authorizeCapability(
      authorizationTurn,
      profile,
      normalizedRequest,
      toolCallId,
      options.resumeCheckpoint?.kind === "approval" ? options.resumeCheckpoint : undefined,
      {
        ...preparedToolResumeContext,
        toolCallId,
      },
      turn,
    );
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.authorization.capability.done", {
        ...shellDebugBase,
        callId: toolCallId,
        request: runtime.tracing.describeCapabilityRequestForDebugLog(normalizedRequest),
        result: runtime.tracing.describeAuthorizationResultForDebugLog(authorization),
      });
    }
    if (authorization !== "allow") {
      const termination = runtime.interruptions.createTurnTerminatedError(authorization);
      await persistTerminalToolFailure(
        toolCallId,
        prepared.normalizedInput,
        undefined,
        termination,
      );
      throw termination;
    }
    executionSandboxProfile = runtime.capability.mergeSandboxProfiles(
      executionSandboxProfile,
      runtime.capability.policyForProfile(profile).evaluate(normalizedRequest).sandboxProfile,
    );
  }

  return { executionSandboxProfile };
}
