package tools

import (
	"context"
	"fmt"
	"os/exec"
	"time"

	"github.com/user/keen-code/internal/filesystem"
)

const (
	bashTimeout   = 180 * time.Second
	maxOutputSize = 10 * 1024 * 1024 // 10MB
)

type BashTool struct {
	guard               *filesystem.Guard
	permissionRequester PermissionRequester
}

func NewBashTool(guard *filesystem.Guard, permissionRequester PermissionRequester) *BashTool {
	return &BashTool{
		guard:               guard,
		permissionRequester: permissionRequester,
	}
}

func (t *BashTool) Name() string {
	return "bash"
}

func (t *BashTool) Description() string {
	return `Execute bash commands in the terminal. This is a fallback tool — prefer
dedicated tools when they exist:
- read_file for reading files
- write_file for creating files
- edit_file for modifying files
- glob for finding files by name
- grep for searching file contents

Do not use bash for reading files, file search, or content search when read_file,
glob, or grep can do it. For read-only investigation, prefer dedicated tools
because their output is structured for later reasoning.

Use this for: running tests, installing dependencies, git operations, build commands,
and other shell-native tasks not covered by dedicated tools.

IMPORTANT:
- Set isDangerous=true for any potentially dangerous commands. This will always prompt for user permission.
- Examples of dangerous commands:
  - removing files or directories
  - git operations that modify the repository like commit, push, reset, rebase, etc.
  - killing processes
  - modifying system files
  - accessing environment variables
- Examples of non-dangerous commands:
  - adding to git stage (git add)
  - linting code
  - running tests
  - building the project
  - running the project
  - installing dependencies
- Commands time out after 180 seconds.
- Quote paths that may contain spaces.
- Prefer single commands over long chains. For independent commands, use parallel
  tool calls instead of chaining with &&.`
}

func (t *BashTool) InputSchema() map[string]any {
	return map[string]any{
		"type": "object",
		"properties": map[string]any{
			"command": map[string]any{
				"type":        "string",
				"description": "The bash command to execute. Quote paths with spaces. Prefer simple single commands over long chains.",
			},
			"isDangerous": map[string]any{
				"type":        "boolean",
				"description": "Set to true if the command may modify files or system state (e.g., rm, mv, git commit, package install). This will always prompt for user permission.",
			},
			"summary": map[string]any{
				"type":        "string",
				"description": "A brief 5-10 word description of what the command does",
			},
		},
		"required":             []string{"command"},
		"additionalProperties": false,
	}
}

func (t *BashTool) Execute(ctx context.Context, input any) (any, error) {
	params, ok := input.(map[string]any)
	if !ok {
		return nil, fmt.Errorf("invalid input: expected map[string]any, got %T", input)
	}

	commandValue, ok := params["command"]
	if !ok {
		return nil, fmt.Errorf("invalid input: missing required 'command' parameter")
	}

	command, ok := commandValue.(string)
	if !ok || command == "" {
		return nil, fmt.Errorf("invalid input: command must be a non-empty string")
	}

	isDangerous := false
	if isDangerousValue, exists := params["isDangerous"]; exists {
		if isDangerousBool, ok := isDangerousValue.(bool); ok {
			isDangerous = isDangerousBool
		}
	}

	summary := ""
	if summaryValue, exists := params["summary"]; exists {
		if summaryStr, ok := summaryValue.(string); ok {
			summary = summaryStr
		}
	}

	permission := t.guard.CheckPath(".", "read")

	switch permission {
	case filesystem.PermissionDenied:
		return nil, fmt.Errorf("permission denied by policy")
	case filesystem.PermissionPending:
		if t.permissionRequester == nil {
			return nil, fmt.Errorf("permission denied: user approval required but not available")
		}
		resolvedPath, _ := t.guard.ResolvePath(".")
		allowed, err := t.permissionRequester.RequestPermission(ctx, t.Name(), ".", resolvedPath, false)
		if err != nil {
			return nil, fmt.Errorf("permission request failed: %w", err)
		}
		if !allowed {
			return nil, fmt.Errorf("permission denied by user: bash execution rejected")
		}
	}

	if isDangerous {
		if t.permissionRequester == nil {
			return nil, fmt.Errorf("permission denied: user approval required for dangerous command but not available")
		}
		allowed, err := t.permissionRequester.RequestPermission(ctx, t.Name(), command, "", true)
		if err != nil {
			return nil, fmt.Errorf("permission request failed: %w", err)
		}
		if !allowed {
			return nil, fmt.Errorf("permission denied by user: dangerous command execution rejected")
		}
	}

	return t.executeCommand(ctx, command, summary)
}

func (t *BashTool) executeCommand(ctx context.Context, command, summary string) (any, error) {
	ctx, cancel := context.WithTimeout(ctx, bashTimeout)
	defer cancel()

	cmd := exec.CommandContext(ctx, "bash", "-c", command)

	stdout, err := cmd.Output()

	exitCode := 0
	var stderr []byte

	if err != nil {
		if ctx.Err() == context.DeadlineExceeded {
			return nil, fmt.Errorf("command timed out after %v", bashTimeout)
		}
		if exitErr, ok := err.(*exec.ExitError); ok {
			exitCode = exitErr.ExitCode()
			stderr = exitErr.Stderr
		} else {
			return nil, fmt.Errorf("command execution failed: %w", err)
		}
	}

	stdoutStr := string(stdout)
	stderrStr := string(stderr)

	if len(stdout) > maxOutputSize {
		stdoutStr = stdoutStr[:maxOutputSize] + "\n... (output truncated)"
	}
	if len(stderr) > maxOutputSize {
		stderrStr = stderrStr[:maxOutputSize] + "\n... (stderr truncated)"
	}

	result := map[string]any{
		"command":   command,
		"exit_code": exitCode,
		"stdout":    stdoutStr,
		"stderr":    stderrStr,
	}

	if summary != "" {
		result["summary"] = summary
	}

	return result, nil
}
