package agent

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

type Toolset struct {
	Workspace    string
	Timeout      time.Duration
	MaxOutput    int
	shell        string
	shellArgs    []string
	python       string
	dockerSocket func() string
	apiCfg       *APIConfigStore
	coop         *CoopManager
	// sessionRoot 是各会话独立工作目录的根（data/workspaces），
	// 每个会话在其下拥有 <sessionRoot>/<sessionID> 目录，互不干扰。
	sessionRoot string
}

func NewToolset(cfg Config, dockerCfg *DockerConfigStore, apiCfg *APIConfigStore) *Toolset {
	toolset := &Toolset{
		Workspace:   cfg.Workspace,
		Timeout:     cfg.ToolTimeout,
		MaxOutput:   cfg.MaxToolResultChars,
		apiCfg:      apiCfg,
		sessionRoot: filepath.Join(cfg.DataDir, "workspaces"),
	}

	if dockerCfg != nil {
		toolset.dockerSocket = dockerCfg.Socket
	} else {
		toolset.dockerSocket = defaultDockerSocket
	}

	if shell, err := exec.LookPath("bash"); err == nil {
		toolset.shell = shell
		toolset.shellArgs = []string{"-lc"}
	} else if shell, err := exec.LookPath("pwsh"); err == nil {
		toolset.shell = shell
		toolset.shellArgs = []string{"-NoProfile", "-NonInteractive", "-Command"}
	} else if shell, err := exec.LookPath("powershell"); err == nil {
		toolset.shell = shell
		toolset.shellArgs = []string{"-NoProfile", "-NonInteractive", "-Command"}
	} else {
		toolset.shell = "cmd.exe"
		toolset.shellArgs = []string{"/C"}
	}

	if python, err := exec.LookPath("python"); err == nil {
		toolset.python = python
	} else if python, err := exec.LookPath("python3"); err == nil {
		toolset.python = python
	} else {
		toolset.python = "python"
	}
	return toolset
}

func (t *Toolset) Definitions() []ToolDefinition {
	return []ToolDefinition{
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "run_bash",
				Description: "在用户工作区目录中执行一条 Bash 命令。Windows 上没有 Bash 时自动回退到 PowerShell 或 cmd。适合查看文件、运行构建、安装依赖、搜索代码、启动程序等。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"command": map[string]any{
							"type":        "string",
							"description": "要执行的完整命令，例如 `ls -la` 或 `go test ./...`。",
						},
						"timeout_seconds": map[string]any{
							"type":        "integer",
							"description": "命令超时秒数，默认 120 秒。",
						},
					},
					"required": []string{"command"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "read_file",
				Description: "读取工作区内的文本文件并返回内容。超长文件会被截断。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"path": map[string]any{
							"type":        "string",
							"description": "文件路径，可以是相对路径或绝对路径。必须位于工作区内。",
						},
					},
					"required": []string{"path"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "write_file",
				Description: "写入或覆盖工作区内的文件。父目录不存在时自动创建。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"path": map[string]any{
							"type":        "string",
							"description": "目标文件路径，可以是相对路径或绝对路径。必须位于工作区内。",
						},
						"content": map[string]any{
							"type":        "string",
							"description": "要写入的完整文件内容。",
						},
					},
					"required": []string{"path", "content"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "run_python",
				Description: "在工作区目录中执行一段 Python 代码。适合数据处理、批量修改、生成脚本和自动化任务。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"code": map[string]any{
							"type":        "string",
							"description": "要执行的完整 Python 代码。",
						},
					},
					"required": []string{"code"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "list_directory",
				Description: "列出工作区内目录的内容，包括文件大小和修改时间。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"path": map[string]any{
							"type":        "string",
							"description": "目录路径，默认为工作区根目录。",
						},
					},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "docker_ps",
				Description: "列出 Docker 容器。默认连接本地 Docker，可通过 Web 设置中的 Docker Socket 切换到远程 Docker 服务器。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"all": map[string]any{
							"type":        "boolean",
							"description": "是否列出所有容器（包括已停止的），默认 false 只显示运行中。",
						},
					},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "docker_images",
				Description: "列出 Docker 镜像列表。",
				Parameters: map[string]any{
					"type":       "object",
					"properties": map[string]any{},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "docker_logs",
				Description: "查看 Docker 容器的最近日志。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"container": map[string]any{
							"type":        "string",
							"description": "容器名称或 ID。",
						},
						"tail": map[string]any{
							"type":        "integer",
							"description": "返回最近的日志行数，默认 100。",
						},
					},
					"required": []string{"container"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "docker_inspect",
				Description: "查看 Docker 容器的详细配置和状态信息。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"container": map[string]any{
							"type":        "string",
							"description": "容器名称或 ID。",
						},
					},
					"required": []string{"container"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "docker_exec",
				Description: "在 Docker 容器内执行一条命令（使用 sh -c），返回执行输出。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"container": map[string]any{
							"type":        "string",
							"description": "容器名称或 ID。",
						},
						"command": map[string]any{
							"type":        "string",
							"description": "要在容器内执行的命令，例如 `ls -la` 或 `cat /etc/os-release`。",
						},
					},
					"required": []string{"container", "command"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "docker_start",
				Description: "启动一个 Docker 容器。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"container": map[string]any{
							"type":        "string",
							"description": "容器名称或 ID。",
						},
					},
					"required": []string{"container"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "docker_stop",
				Description: "停止一个 Docker 容器。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"container": map[string]any{
							"type":        "string",
							"description": "容器名称或 ID。",
						},
						"timeout": map[string]any{
							"type":        "integer",
							"description": "等待优雅停止的秒数，默认由 Docker 决定。",
						},
					},
					"required": []string{"container"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolDefinitionFunction{
				Name:        "run_coop",
				Description: "调用协作容器异步完成一个任务（容器内单个 agent 独立执行，自动注入当前 Agent 已配置的模型、Base URL、API Key 与接口协议，无需重复填写）。支持三种引擎：engine=pi（默认，pi-coding-agent，支持 openai+anthropic 协议）、engine=pigo（pigo，支持 openai+anthropic 协议）或 engine=claude（Claude Code，仅支持 anthropic 协议）。需先构建对应镜像（pi-coop / pigo-coop / claude-coop）。该工具是异步的：调用后立即返回任务 ID，容器在后台运行；任务完成（成功 / 失败 / 超时）后系统会自动注入一条【协作任务完成通知】消息（含结构化结果摘要），由你确认结果、必要时提交 flag 并向用户汇报，无需在此等待。",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"task": map[string]any{
							"type":        "string",
							"description": "要交给协作容器完成的任务描述，例如“请为 XX 编写设计文档并实现原型”。解题类任务需写明目标地址、平台提交规则（BENCHMARK_TOKEN / unique_code / submit API）。",
						},
						"engine": map[string]any{
							"type":        "string",
							"description": "协作引擎：pi（默认，pi-coding-agent）、pigo（pigo）或 claude（Claude Code，仅支持 anthropic 协议）。pi 与 pigo 支持 openai+anthropic 协议；claude 仅支持 anthropic 协议。",
							"enum":         []string{"pi", "pigo", "claude"},
						},
						"round_max": map[string]any{
							"type":        "integer",
							"description": "最大轮次，默认 1（单 agent 一次运行完成，未完成则失败并重新下发）。",
						},
						"timeout": map[string]any{
							"type":        "integer",
							"description": "单轮超时秒数，默认 1800（30 分钟）。单 agent 需在此时限内完成全部工作并创建 DONE。",
						},
						"blackboard": map[string]any{
							"type":        "string",
							"description": "可选：主机上的目录路径，挂载到容器 /blackboard 保留黑板产物与会话；Windows 路径会自动转换为 WSL 挂载路径（/mnt/盘符/...）。留空则自动使用本会话工作区下的独立子目录 blackboard/<任务ID>（同一会话多次协作互不污染）。",
						},
					},
					"required": []string{"task"},
				},
			},
		},
	}
}

func (t *Toolset) Execute(ctx context.Context, sessionID, name, arguments string) ToolResult {
	var params map[string]any
	if err := json.Unmarshal([]byte(arguments), &params); err != nil {
		return ToolResult{Success: false, Output: "无法解析工具参数: " + err.Error()}
	}

	switch name {
	case "run_bash":
		command, _ := params["command"].(string)
		if strings.TrimSpace(command) == "" {
			return ToolResult{Success: false, Output: "command 不能为空"}
		}
		return t.runShell(ctx, sessionID, command, intParam(params, "timeout_seconds"))
	case "read_file":
		path, _ := params["path"].(string)
		return t.readFile(sessionID, path)
	case "write_file":
		path, _ := params["path"].(string)
		content, _ := params["content"].(string)
		return t.writeFile(sessionID, path, content)
	case "run_python":
		code, _ := params["code"].(string)
		if strings.TrimSpace(code) == "" {
			return ToolResult{Success: false, Output: "code 不能为空"}
		}
		return t.runPython(ctx, sessionID, code)
	case "list_directory":
		path, _ := params["path"].(string)
		return t.listDirectory(sessionID, path)
	case "docker_ps":
		return t.dockerPS(ctx, params)
	case "docker_images":
		return t.dockerImages(ctx, params)
	case "docker_logs":
		return t.dockerLogs(ctx, params)
	case "docker_inspect":
		return t.dockerInspect(ctx, params)
	case "docker_exec":
		return t.dockerExec(ctx, params)
	case "docker_start":
		return t.dockerStart(ctx, params)
	case "docker_stop":
		return t.dockerStop(ctx, params)
	case "run_coop":
		return t.runCoop(ctx, sessionID, params)
	default:
		return ToolResult{Success: false, Output: "未知工具: " + name}
	}
}

func (t *Toolset) runShell(ctx context.Context, sessionID, command string, timeoutSeconds int) ToolResult {
	// 不信任 LLM 传入的超长 timeout：超过全局工具超时（默认 120s）一律截断为全局值。
	// 否则 sleep / 轮询类命令会长时间阻塞主 Agent（持有会话锁），期间前端
	// 长时间收不到事件会被判"任务中断"，且 coop 完成通知也拿不到锁无法及时处理。
	if timeoutSeconds <= 0 || timeoutSeconds > int(t.Timeout.Seconds()) {
		timeoutSeconds = int(t.Timeout.Seconds())
	}
	ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
	defer cancel()

	var stdout, stderr bytes.Buffer
	cmd := exec.CommandContext(ctx, t.shell, t.shellArgs...)
	cmd.Args = append(cmd.Args, command)
	cmd.Dir = t.workspaceFor(sessionID)
	cmd.Env = append(os.Environ(), "AGENT_WORKSPACE="+t.workspaceFor(sessionID))
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()
	var out strings.Builder
	out.WriteString(strings.TrimSpace(stdout.String()))
	if stderrText := strings.TrimSpace(stderr.String()); stderrText != "" {
		if out.Len() > 0 {
			out.WriteString("\n")
		}
		out.WriteString("[stderr]\n")
		out.WriteString(stderrText)
	}
	if err != nil {
		if out.Len() > 0 {
			out.WriteString("\n")
		}
		out.WriteString("[命令失败] " + err.Error())
	}
	return ToolResult{Success: err == nil, Output: t.truncate(out.String())}
}

func (t *Toolset) runPython(ctx context.Context, sessionID, code string) ToolResult {
	ctx, cancel := context.WithTimeout(ctx, t.Timeout)
	defer cancel()

	var stdout, stderr bytes.Buffer
	cmd := exec.CommandContext(ctx, t.python, "-c", code)
	cmd.Dir = t.workspaceFor(sessionID)
	cmd.Env = append(os.Environ(), "AGENT_WORKSPACE="+t.workspaceFor(sessionID))
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()
	var out strings.Builder
	out.WriteString(strings.TrimSpace(stdout.String()))
	if stderrText := strings.TrimSpace(stderr.String()); stderrText != "" {
		if out.Len() > 0 {
			out.WriteString("\n")
		}
		out.WriteString("[stderr]\n")
		out.WriteString(stderrText)
	}
	if err != nil {
		if out.Len() > 0 {
			out.WriteString("\n")
		}
		out.WriteString("[运行失败] " + err.Error())
	}
	return ToolResult{Success: err == nil, Output: t.truncate(out.String())}
}

func (t *Toolset) readFile(sessionID, path string) ToolResult {
	absPath, err := t.resolvePath(sessionID, path)
	if err != nil {
		return ToolResult{Success: false, Output: err.Error()}
	}
	data, err := os.ReadFile(absPath)
	if err != nil {
		return ToolResult{Success: false, Output: err.Error()}
	}
	return ToolResult{Success: true, Output: t.truncate(string(data))}
}

func (t *Toolset) writeFile(sessionID, path, content string) ToolResult {
	absPath, err := t.resolvePath(sessionID, path)
	if err != nil {
		return ToolResult{Success: false, Output: err.Error()}
	}
	if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
		return ToolResult{Success: false, Output: err.Error()}
	}
	if err := os.WriteFile(absPath, []byte(content), 0o644); err != nil {
		return ToolResult{Success: false, Output: err.Error()}
	}
	return ToolResult{Success: true, Output: fmt.Sprintf("已写入 %d 字节到 %s", len(content), absPath)}
}

func (t *Toolset) listDirectory(sessionID, path string) ToolResult {
	absPath, err := t.resolvePath(sessionID, path)
	if err != nil {
		return ToolResult{Success: false, Output: err.Error()}
	}
	entries, err := os.ReadDir(absPath)
	if err != nil {
		return ToolResult{Success: false, Output: err.Error()}
	}
	sort.Slice(entries, func(i, j int) bool {
		return entries[i].Name() < entries[j].Name()
	})

	var out strings.Builder
	for _, entry := range entries {
		info, infoErr := entry.Info()
		if infoErr != nil {
			continue
		}
		name := entry.Name()
		if entry.IsDir() {
			name += "/"
		}
		fmt.Fprintf(&out, "%-40s %10d  %s\n", name, info.Size(), info.ModTime().Format("2006-01-02 15:04"))
	}
	if out.Len() == 0 {
		return ToolResult{Success: true, Output: "目录为空"}
	}
	return ToolResult{Success: true, Output: t.truncate(out.String())}
}

// resolvePath 把工具传入的路径解析为绝对路径：
// - 相对路径基于该会话的独立工作目录（data/workspaces/<sessionID>）；
// - 绝对路径允许在项目根目录内（协作黑板产物、agent 自身文件等共享内容）；
// - 项目根目录之外一律拒绝。
func (t *Toolset) resolvePath(sessionID, raw string) (string, error) {
	if strings.TrimSpace(raw) == "" {
		raw = "."
	}
	path := filepath.FromSlash(raw)
	if !filepath.IsAbs(path) {
		path = filepath.Join(t.workspaceFor(sessionID), path)
	}
	absPath, err := filepath.Abs(path)
	if err != nil {
		return "", err
	}
	if !pathWithin(absPath, t.Workspace) {
		return "", errors.New("路径超出项目根目录范围，已拒绝: " + absPath)
	}
	return absPath, nil
}

// workspaceFor 返回某会话的独立工作目录并确保其存在。
func (t *Toolset) workspaceFor(sessionID string) string {
	root := t.sessionRoot
	if root == "" {
		root = t.Workspace
	}
	dir := filepath.Join(root, sessionID)
	_ = os.MkdirAll(dir, 0o755)
	return dir
}

// pathWithin 判断 child 是否位于 parent 目录内（含自身）。
func pathWithin(child, parent string) bool {
	rel, err := filepath.Rel(parent, child)
	if err != nil {
		return false
	}
	return rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)))
}

func (t *Toolset) truncate(value string) string {
	runes := []rune(value)
	if len(runes) <= t.MaxOutput {
		return value
	}
	return string(runes[:t.MaxOutput]) + "\n...[输出过长，已截断]"
}

func intParam(params map[string]any, key string) int {
	switch value := params[key].(type) {
	case float64:
		return int(value)
	case int:
		return value
	default:
		return 0
	}
}
