package tools

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

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

// ListFilesTool lists files in a directory, optionally through a sandbox container.
type ListFilesTool struct {
	workspace       string
	restrict        bool
	deniedPrefixes  []string // path prefixes to deny access to (e.g. .goclaw)
	sandboxMgr      sandbox.Manager
	contextFileIntc *ContextFileInterceptor // unused, satisfies InterceptorAware
	memIntc         *MemoryInterceptor      // nil = no memory routing
}

func (t *ListFilesTool) SetContextFileInterceptor(intc *ContextFileInterceptor) {
	t.contextFileIntc = intc
}

func (t *ListFilesTool) SetMemoryInterceptor(intc *MemoryInterceptor) {
	t.memIntc = intc
}

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

func NewListFilesTool(workspace string, restrict bool) *ListFilesTool {
	return &ListFilesTool{workspace: workspace, restrict: restrict}
}

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

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

func (t *ListFilesTool) Name() string        { return "list_files" }
func (t *ListFilesTool) Description() string { return "List files and directories in a path" }
func (t *ListFilesTool) Parameters() map[string]any {
	return map[string]any{
		"type": "object",
		"properties": map[string]any{
			"path": map[string]any{
				"type":        "string",
				"description": "Directory path (relative to workspace; omit for workspace root)",
			},
		},
	}
}

func (t *ListFilesTool) Execute(ctx context.Context, args map[string]any) *Result {
	path, _ := args["path"].(string)
	if path == "" {
		path = "."
	}

	// Virtual FS: route memory directory listing to DB
	if t.memIntc != nil {
		if listing, handled, err := t.memIntc.ListFiles(ctx, path); handled {
			if err != nil {
				return ErrorResult(fmt.Sprintf("failed to list memory files: %v", err))
			}
			if listing == "" {
				return SilentResult("No memory files stored yet")
			}
			return SilentResult(listing)
		}
	}

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

	// 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())
	}

	entries, err := os.ReadDir(resolved)
	if err != nil {
		if os.IsNotExist(err) {
			msg := fmt.Sprintf("Directory does not exist: %s", path)
			if teamWs := ToolTeamWorkspaceFromCtx(ctx); teamWs != "" && !strings.HasPrefix(resolved, teamWs) {
				msg += fmt.Sprintf("\nHint: try the team workspace path: list_files(path=\"%s/%s\")", teamWs, path)
			}
			return SilentResult(msg)
		}
		return ErrorResult(fmt.Sprintf("failed to list directory: %v", err))
	}

	var sb strings.Builder
	for _, entry := range entries {
		// Filter out denied entries (both files and directories) from listing.
		if len(t.deniedPrefixes) > 0 {
			entryPath := filepath.Join(resolved, entry.Name())
			if checkDeniedPath(entryPath, t.workspace, t.deniedPrefixes) != nil {
				continue
			}
		}

		info, _ := entry.Info()
		if entry.IsDir() {
			fmt.Fprintf(&sb, "[DIR]  %s/\n", entry.Name())
		} else if info != nil {
			fmt.Fprintf(&sb, "[FILE] %s (%d bytes)\n", entry.Name(), info.Size())
		} else {
			fmt.Fprintf(&sb, "[FILE] %s\n", entry.Name())
		}
	}

	return SilentResult(sb.String())
}

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

	output, err := bridge.ListDir(ctx, path)
	if err != nil {
		return ErrorResult(fmt.Sprintf("failed to list directory: %v", err))
	}

	return SilentResult(output)
}

func (t *ListFilesTool) 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
}
