//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

// CodeExec sandbox client. The Python sandbox service is kept
// as-is (we do NOT rewrite the sandbox). The Go side has a
// client that talks to the Python sandbox subsystem via the
// internal/agent/sandbox providers.
//
// The Python agent's code_exec delegates to a
// `SandboxProvider` subsystem (agent/sandbox/providers/) with
// three backends: self-managed (Docker + gVisor via local
// executor_manager HTTP), Aliyun Code Interpreter (cloud), and
// e2b (cloud SaaS). A `ProviderManager` selects one at
// startup. The Go side's decision is recorded in
// code_exec_sandbox_design.md:
//
//  1. shell out = spawn a Python subprocess that uses
//     ProviderManager to dispatch to whichever provider the
//     operator has configured. Reuses all three providers.
//  2. in-process = reimplement all three provider clients in
//     Go. No Python dep, but three SDK surfaces to maintain.
//
// Decision: shell out (option A). The interface here is stable —
// when the implementation lands, callers see no change.
//
// Until the proto lands, CodeExecTool.InvokableRun surfaces
// ErrCodeExecSandboxMissing (defined in code_exec.go) so callers
// can detect the no-sandbox state.
package tool

import (
	"context"
	"sync"
)

// SandboxClient is the abstract interface for the CodeExec gRPC
// client. Production code calls SetSandboxClient at boot to install
// a real client; the default is a stub returning
// ErrSandboxNotWired.
type SandboxClient interface {
	ExecuteCode(ctx context.Context, req SandboxRequest) (*SandboxResponse, error)
}

// SandboxRequest is the wire shape between the CodeExec tool and
// the sandbox subsystem. Mirrors `agent.sandbox.client.execute_code`'s
// input surface. Arguments and Timeout are optional — the bridge
// applies defaults (no args, 30s timeout) when zero-valued.
type SandboxRequest struct {
	Lang      string         // "python" | "javascript"
	Script    string         // the user's code
	Arguments map[string]any // optional, passed to main(**args)
	Timeout   int            // seconds, 0 = use provider default (30s)
}

// SandboxResponse is what the sandbox returns. Stdout / Stderr are
// captured streams; Returned is the legacy alias for the
// structured main() return value; StructuredResult carries the
// full extracted payload; Metadata holds provider-specific extras.
type SandboxResponse struct {
	Stdout           string
	Stderr           string
	Returned         string
	ExitCode         int
	StructuredResult map[string]any
	Metadata         map[string]any
}

// ErrSandboxNotWired is the sentinel returned by the default stub
// client. Kept as an alias of ErrCodeExecSandboxMissing for backward
// compatibility with existing code_exec_test.go callers — the tool's
// public error surface is ErrCodeExecSandboxMissing; the client
// surface here is the same condition, named for code-path clarity.
var ErrSandboxNotWired = ErrCodeExecSandboxMissing

var (
	sandboxClientMu   sync.RWMutex
	sandboxClientImpl SandboxClient = stubSandboxClient{}
)

func SetSandboxClient(c SandboxClient) {
	sandboxClientMu.Lock()
	defer sandboxClientMu.Unlock()
	if c == nil {
		sandboxClientImpl = stubSandboxClient{}
		return
	}
	sandboxClientImpl = c
}

func GetSandboxClient() SandboxClient {
	sandboxClientMu.RLock()
	defer sandboxClientMu.RUnlock()
	return sandboxClientImpl
}

type stubSandboxClient struct{}

func (stubSandboxClient) ExecuteCode(_ context.Context, _ SandboxRequest) (*SandboxResponse, error) {
	return nil, ErrSandboxNotWired
}

func (stubSandboxClient) IsStubSandboxClient() bool { return true }
