package tools

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/nextlevelbuilder/goclaw/internal/bus"
	"github.com/nextlevelbuilder/goclaw/internal/sandbox"
	"github.com/nextlevelbuilder/goclaw/internal/store"
)

// WriteFileTool writes content to a file, optionally through a sandbox container.
type WriteFileTool struct {
	workspace        string
	restrict         bool
	deniedPrefixes   []string // path prefixes to deny access to (e.g. .goclaw)
	sandboxMgr       sandbox.Manager
	contextFileIntc  *ContextFileInterceptor // nil = no virtual FS routing
	memIntc          *MemoryInterceptor      // nil = no memory routing
	groupWriterCache *store.GroupWriterCache // nil = no group write restriction
	workspaceIntc    *WorkspaceInterceptor   // nil = no team workspace validation
}

// DenyPaths adds path prefixes that write_file must reject.
func (t *WriteFileTool) DenyPaths(prefixes ...string) {
	t.deniedPrefixes = append(t.deniedPrefixes, prefixes...)
}

// SetContextFileInterceptor enables virtual FS routing for context files.
func (t *WriteFileTool) SetContextFileInterceptor(intc *ContextFileInterceptor) {
	t.contextFileIntc = intc
}

// SetMemoryInterceptor enables virtual FS routing for memory files.
func (t *WriteFileTool) SetMemoryInterceptor(intc *MemoryInterceptor) {
	t.memIntc = intc
}

// SetGroupWriterCache enables group write permission checks.
func (t *WriteFileTool) SetGroupWriterCache(c *store.GroupWriterCache) {
	t.groupWriterCache = c
}

// SetWorkspaceInterceptor enables team workspace validation and event broadcasting.
func (t *WriteFileTool) SetWorkspaceInterceptor(intc *WorkspaceInterceptor) {
	t.workspaceIntc = intc
}

func NewWriteFileTool(workspace string, restrict bool) *WriteFileTool {
	return &WriteFileTool{workspace: workspace, restrict: restrict}
}

func NewSandboxedWriteFileTool(workspace string, restrict bool, mgr sandbox.Manager) *WriteFileTool {
	return &WriteFileTool{workspace: workspace, restrict: restrict, sandboxMgr: mgr}
}

// SetSandboxKey is a no-op; sandbox key is now read from ctx (thread-safe).
func (t *WriteFileTool) SetSandboxKey(key string) {}

func (t *WriteFileTool) Name() string { return "write_file" }
func (t *WriteFileTool) Description() string {
	return "Write content to a file, creating directories as needed. " +
		"IMPORTANT: content longer than ~12000 characters may be truncated by the API. " +
		"For large files, use the edit tool to build the file in sections, or split into multiple write_file calls with append=true."
}
func (t *WriteFileTool) Parameters() map[string]any {
	return map[string]any{
		"type": "object",
		"properties": map[string]any{
			"path": map[string]any{
				"type":        "string",
				"description": "File path (relative to workspace, or absolute)",
			},
			"content": map[string]any{
				"type":        "string",
				"description": "Content to write",
			},
			"append": map[string]any{
				"type":        "boolean",
				"description": "Append content to the file instead of overwriting. Use this to build large files in chunks.",
			},
			"deliver": map[string]any{
				"type":        "boolean",
				"description": "Deliver this file to the user as an attachment. Defaults to true. Set to false for intermediate/temporary files (e.g. config, cache, temp scripts).",
			},
		},
		"required": []string{"path", "content"},
	}
}

func (t *WriteFileTool) Execute(ctx context.Context, args map[string]any) *Result {
	path, _ := args["path"].(string)
	content, _ := args["content"].(string)
	appendMode, _ := args["append"].(bool)
	deliver := true
	if v, ok := args["deliver"].(bool); ok {
		deliver = v
	}
	if path == "" {
		return ErrorResult("path is required")
	}

	// Group write permission check
	if t.groupWriterCache != nil {
		if err := store.CheckGroupWritePermission(ctx, t.groupWriterCache); err != nil {
			return ErrorResult(err.Error())
		}
	}

	// Virtual FS: route context files to DB
	if t.contextFileIntc != nil {
		if handled, err := t.contextFileIntc.WriteFile(ctx, path, content); handled {
			if err != nil {
				return ErrorResult(fmt.Sprintf("failed to write context file: %v", err))
			}
			return SilentResult(fmt.Sprintf("Context file written: %s (%d bytes)", path, len(content)))
		}
	}

	// Virtual FS: route memory files to DB
	if t.memIntc != nil {
		if mwr, err := t.memIntc.WriteFile(ctx, path, content); mwr.Handled {
			if err != nil {
				return ErrorResult(fmt.Sprintf("failed to write memory file: %v", err))
			}
			msg := fmt.Sprintf("Memory file written: %s (%d bytes)", path, len(content))
			if mwr.KGTriggered {
				msg += "\n\n[Knowledge graph extraction triggered in background. The knowledge system may take a moment to fully update with new entities and relationships.]"
			}
			return SilentResult(msg)
		}
	}

	// Sandbox routing (sandboxKey from ctx — thread-safe)
	sandboxKey := ToolSandboxKeyFromCtx(ctx)
	if t.sandboxMgr != nil && sandboxKey != "" {
		return t.executeInSandbox(ctx, path, content, sandboxKey, deliver)
	}

	// Host execution — use per-user workspace from context if available
	workspace := ToolWorkspaceFromCtx(ctx)
	if workspace == "" {
		workspace = t.workspace
	}
	allowed := allowedWithTeamWorkspace(ctx, nil)
	resolved, err := resolvePathWithAllowed(path, workspace, effectiveRestrict(ctx, t.restrict), allowed)
	if err != nil {
		return ErrorResult(err.Error())
	}
	if err := checkDeniedPath(resolved, t.workspace, t.deniedPrefixes); err != nil {
		return ErrorResult(err.Error())
	}

	// Team workspace validation + delete-on-empty.
	if t.workspaceIntc != nil {
		isDelete, intcErr := t.workspaceIntc.HandleWrite(ctx, resolved, content)
		if intcErr != nil {
			return ErrorResult(intcErr.Error())
		}
		if isDelete {
			if err := os.Remove(resolved); err != nil && !os.IsNotExist(err) {
				return ErrorResult(fmt.Sprintf("failed to delete file: %v", err))
			}
			t.workspaceIntc.AfterWrite(ctx, resolved, "delete")
			return SilentResult(fmt.Sprintf("File deleted: %s", path))
		}
	}

	if err := os.MkdirAll(filepath.Dir(resolved), 0755); err != nil {
		return ErrorResult(fmt.Sprintf("failed to create directory: %v", err))
	}

	if appendMode {
		f, err := os.OpenFile(resolved, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
		if err != nil {
			return ErrorResult(fmt.Sprintf("failed to open file for append: %v", err))
		}
		_, err = f.WriteString(content)
		f.Close()
		if err != nil {
			return ErrorResult(fmt.Sprintf("failed to append to file: %v", err))
		}
	} else if err := os.WriteFile(resolved, []byte(content), 0644); err != nil {
		return ErrorResult(fmt.Sprintf("failed to write file: %v", err))
	}

	if t.workspaceIntc != nil {
		t.workspaceIntc.AfterWrite(ctx, resolved, "write")
	}

	verb := "written"
	if appendMode {
		verb = "appended"
	}
	msg := fmt.Sprintf("File %s: %s (%d bytes)", verb, path, len(content))
	if deliver {
		msg += ". File will be automatically delivered to the user — do NOT send it again via message tool."
	}
	result := SilentResult(msg)
	result.Deliverable = content
	if deliver {
		result.Media = []bus.MediaFile{{Path: resolved}}
	}
	return result
}

func (t *WriteFileTool) executeInSandbox(ctx context.Context, path, content, sandboxKey string, deliver bool) *Result {
	bridge, err := t.getFsBridge(ctx, sandboxKey)
	if err != nil {
		return ErrorResult(fmt.Sprintf("sandbox error: %v", err))
	}

	if err := bridge.WriteFile(ctx, path, content); err != nil {
		return ErrorResult(fmt.Sprintf("failed to write file: %v", err))
	}

	msg := fmt.Sprintf("File written: %s (%d bytes)", path, len(content))
	if deliver {
		msg += ". File will be automatically delivered to the user — do NOT send it again via message tool."
	}
	result := SilentResult(msg)
	result.Deliverable = content
	if deliver {
		// Sandbox workspace is bind-mounted — resolve to host path for delivery
		workspace := ToolWorkspaceFromCtx(ctx)
		if workspace == "" {
			workspace = t.workspace
		}
		hostPath := filepath.Join(workspace, path)
		result.Media = []bus.MediaFile{{Path: hostPath}}
	}
	return result
}

func (t *WriteFileTool) getFsBridge(ctx context.Context, sandboxKey string) (*sandbox.FsBridge, error) {
	sb, err := t.sandboxMgr.Get(ctx, sandboxKey, t.workspace, SandboxConfigFromCtx(ctx))
	if err != nil {
		return nil, err
	}
	return sandbox.NewFsBridge(sb.ID(), "/workspace"), nil
}
