package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/YspCoder/clawgo/pkg/nodes"
)

// NodesTool provides an OpenClaw-style control surface for paired nodes.
type NodesTool struct {
	manager   *nodes.Manager
	router    *nodes.Router
	auditPath string
}

const nodeAuditArtifactPreviewLimit = 32768

func NewNodesTool(m *nodes.Manager, r *nodes.Router, auditPath string) *NodesTool {
	return &NodesTool{manager: m, router: r, auditPath: strings.TrimSpace(auditPath)}
}
func (t *NodesTool) Name() string { return "nodes" }
func (t *NodesTool) Description() string {
	return "Manage paired nodes (status/describe/run/invoke/camera/screen/location/canvas)."
}
func (t *NodesTool) Parameters() map[string]interface{} {
	return map[string]interface{}{"type": "object", "properties": map[string]interface{}{
		"action":         map[string]interface{}{"type": "string", "description": "status|describe|run|invoke|agent_task|camera_snap|camera_clip|screen_record|screen_snapshot|location_get|canvas_snapshot|canvas_action"},
		"node":           map[string]interface{}{"type": "string", "description": "target node id"},
		"mode":           map[string]interface{}{"type": "string", "description": "auto|p2p|relay (default auto)"},
		"args":           map[string]interface{}{"type": "object", "description": "action args"},
		"artifact_paths": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}, "description": "optional workspace-relative file paths to bring back as artifacts for agent_task"},
		"task":           map[string]interface{}{"type": "string", "description": "agent_task content for child node model"},
		"model":          map[string]interface{}{"type": "string", "description": "optional model for agent_task"},
		"command":        map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}, "description": "run command array shortcut"},
		"facing":         map[string]interface{}{"type": "string", "description": "camera facing: front|back|both"},
		"duration_ms":    map[string]interface{}{"type": "integer", "description": "clip/record duration"},
	}, "required": []string{"action"}}
}

func (t *NodesTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) {
	_ = ctx
	action := strings.TrimSpace(strings.ToLower(MapStringArg(args, "action")))
	if action == "" {
		return "", fmt.Errorf("action is required")
	}
	nodeID := MapStringArg(args, "node")
	mode := MapStringArg(args, "mode")
	if t.manager == nil {
		return "", fmt.Errorf("nodes manager not configured")
	}
	var statusHandler func() (string, error)
	handlers := map[string]func() (string, error){
		"status": func() (string, error) {
			if nodeID != "" {
				n, ok := t.manager.Get(nodeID)
				if !ok {
					return "", fmt.Errorf("node not found: %s", nodeID)
				}
				b, _ := json.Marshal(n)
				return string(b), nil
			}
			b, _ := json.Marshal(t.manager.List())
			return string(b), nil
		},
	}
	statusHandler = handlers["status"]
	handlers["describe"] = func() (string, error) { return statusHandler() }
	if handler := handlers[action]; handler != nil {
		return handler()
	}
	reqArgs := map[string]interface{}{}
	if raw, ok := args["args"].(map[string]interface{}); ok {
		for k, v := range raw {
			reqArgs[k] = v
		}
	}
	if rawPaths := MapStringListArg(args, "artifact_paths"); len(rawPaths) > 0 {
		reqArgs["artifact_paths"] = rawPaths
	}
	if cmd, ok := args["command"].([]interface{}); ok && len(cmd) > 0 {
		reqArgs["command"] = cmd
	}
	if facing := MapStringArg(args, "facing"); facing != "" {
		f := strings.ToLower(strings.TrimSpace(facing))
		if f != "front" && f != "back" && f != "both" {
			return "", fmt.Errorf("invalid_args: facing must be front|back|both")
		}
		reqArgs["facing"] = f
	}
	if di := MapIntArg(args, "duration_ms", 0); di > 0 {
		if di <= 0 || di > 300000 {
			return "", fmt.Errorf("invalid_args: duration_ms must be in 1..300000")
		}
		reqArgs["duration_ms"] = di
	}
	task := MapStringArg(args, "task")
	model := MapStringArg(args, "model")
	if action == "agent_task" && strings.TrimSpace(task) == "" {
		return "", fmt.Errorf("invalid_args: agent_task requires task")
	}
	if action == "canvas_action" {
		if act := MapStringArg(reqArgs, "action"); act == "" {
			return "", fmt.Errorf("invalid_args: canvas_action requires args.action")
		}
	}
	if nodeID == "" {
		if picked, ok := t.manager.PickRequest(nodes.Request{Action: action, Task: task, Model: model, Args: reqArgs}, mode); ok {
			nodeID = picked.ID
		}
	}
	if nodeID == "" {
		return "", fmt.Errorf("no eligible node found for action=%s", action)
	}
	req := nodes.Request{Action: action, Node: nodeID, Task: task, Model: model, Args: reqArgs}
	if !t.manager.SupportsRequest(nodeID, req) {
		return "", fmt.Errorf("node %s does not support action=%s", nodeID, action)
	}
	if t.router == nil {
		return "", fmt.Errorf("nodes transport router not configured")
	}
	started := time.Now()
	resp, err := t.router.Dispatch(ctx, req, mode)
	durationMs := int(time.Since(started).Milliseconds())
	if err != nil {
		t.writeAudit(req, nodes.Response{OK: false, Code: "transport_error", Error: err.Error(), Node: nodeID, Action: action}, mode, durationMs)
		return "", err
	}
	t.writeAudit(req, resp, mode, durationMs)
	b, _ := json.Marshal(resp)
	return string(b), nil
}

func (t *NodesTool) writeAudit(req nodes.Request, resp nodes.Response, mode string, durationMs int) {
	if strings.TrimSpace(t.auditPath) == "" {
		return
	}
	_ = os.MkdirAll(filepath.Dir(t.auditPath), 0755)
	row := map[string]interface{}{
		"time":        time.Now().UTC().Format(time.RFC3339),
		"mode":        mode,
		"action":      req.Action,
		"node":        req.Node,
		"task":        req.Task,
		"model":       req.Model,
		"ok":          resp.OK,
		"code":        resp.Code,
		"error":       resp.Error,
		"duration_ms": durationMs,
	}
	if len(req.Args) > 0 {
		row["request_args"] = req.Args
	}
	if used := MapStringArg(resp.Payload, "used_transport"); used != "" {
		row["used_transport"] = used
	}
	if fallback := MapStringArg(resp.Payload, "fallback_from"); fallback != "" {
		row["fallback_from"] = fallback
	}
	if count, kinds := artifactAuditSummary(resp.Payload["artifacts"]); count > 0 {
		row["artifact_count"] = count
		if len(kinds) > 0 {
			row["artifact_kinds"] = kinds
		}
		if previews := artifactAuditPreviews(resp.Payload["artifacts"]); len(previews) > 0 {
			row["artifacts"] = previews
		}
	}
	b, _ := json.Marshal(row)
	f, err := os.OpenFile(t.auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return
	}
	defer f.Close()
	_, _ = f.Write(append(b, '\n'))
}

func artifactAuditSummary(raw interface{}) (int, []string) {
	items, ok := raw.([]interface{})
	if !ok {
		if typed, ok := raw.([]map[string]interface{}); ok {
			items = make([]interface{}, 0, len(typed))
			for _, item := range typed {
				items = append(items, item)
			}
		}
	}
	if len(items) == 0 {
		return 0, nil
	}
	kinds := make([]string, 0, len(items))
	for _, item := range items {
		row, ok := item.(map[string]interface{})
		if !ok {
			continue
		}
		if kind := MapStringArg(row, "kind"); kind != "" {
			kinds = append(kinds, kind)
		}
	}
	return len(items), kinds
}

func artifactAuditPreviews(raw interface{}) []map[string]interface{} {
	items, ok := raw.([]interface{})
	if !ok {
		if typed, ok := raw.([]map[string]interface{}); ok {
			items = make([]interface{}, 0, len(typed))
			for _, item := range typed {
				items = append(items, item)
			}
		}
	}
	if len(items) == 0 {
		return nil
	}
	out := make([]map[string]interface{}, 0, len(items))
	for _, item := range items {
		row, ok := item.(map[string]interface{})
		if !ok || len(row) == 0 {
			continue
		}
		entry := map[string]interface{}{}
		for _, key := range []string{"name", "kind", "mime_type", "storage", "path", "url", "source_path"} {
			if value, ok := row[key]; ok && strings.TrimSpace(fmt.Sprint(value)) != "" {
				entry[key] = value
			}
		}
		if size, ok := row["size_bytes"]; ok {
			entry["size_bytes"] = size
		}
		if text := MapStringArg(row, "content_text"); text != "" {
			entry["content_text"] = trimAuditContent(text)
		}
		if b64 := MapStringArg(row, "content_base64"); b64 != "" {
			entry["content_base64"] = trimAuditContent(b64)
			entry["content_base64_truncated"] = len(b64) > nodeAuditArtifactPreviewLimit
		}
		if truncated, ok := MapBoolArg(row, "truncated"); ok && truncated {
			entry["truncated"] = true
		}
		if len(entry) > 0 {
			out = append(out, entry)
		}
	}
	return out
}

func trimAuditContent(raw string) string {
	raw = strings.TrimSpace(raw)
	if len(raw) <= nodeAuditArtifactPreviewLimit {
		return raw
	}
	return raw[:nodeAuditArtifactPreviewLimit]
}
