package agent

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"
)

type DelegateTaskParams struct {
	Task        string `json:"task" description:"Description of the sub-task to delegate"`
	Context     string `json:"context,omitempty" description:"Additional context for the sub-task"`
	Timeout     int    `json:"timeout,omitempty" description:"Timeout per sub-task in seconds (default 120, max 300)"`
	MaxParallel int    `json:"max_parallel,omitempty" description:"Maximum parallel sub-tasks (default 3, max 5)"`
}

type DelegateMultiParams struct {
	Tasks       []DelegateTaskParams `json:"tasks" description:"List of sub-tasks to execute in parallel"`
	MaxParallel int                  `json:"max_parallel,omitempty" description:"Maximum parallel sub-tasks (default 3, max 5)"`
}

type SubTaskResult struct {
	Task    string `json:"task"`
	Success bool   `json:"success"`
	Result  string `json:"result"`
	Error   string `json:"error,omitempty"`
}

type DelegateResponse struct {
	TotalTasks int             `json:"total_tasks"`
	Successful int             `json:"successful"`
	Failed     int             `json:"failed"`
	Results    []SubTaskResult `json:"results"`
	Duration   string          `json:"duration"`
}

func NewDelegateTool(registry *Registry) (*ToolDefinition, error) {
	return NewTool("delegate_task",
		"Delegate one or more tasks for parallel execution. Each sub-task runs in isolation with its own context. Returns aggregated results from all sub-tasks. Use for independent tasks that can run concurrently.",
		func(ctx context.Context, p DelegateTaskParams) (ToolResponse, error) {
			if p.Task == "" {
				return TextErrorResponse("task is required"), nil
			}

			timeout := time.Duration(p.Timeout) * time.Second
			if timeout == 0 {
				timeout = 120 * time.Second
			}
			if timeout > 300*time.Second {
				timeout = 300 * time.Second
			}

			result := executeSubTask(ctx, p.Task, p.Context, timeout, registry)
			resp := DelegateResponse{
				TotalTasks: 1,
				Successful: 0,
				Results:    []SubTaskResult{result},
				Duration:   "N/A",
			}
			if result.Success {
				resp.Successful = 1
			} else {
				resp.Failed = 1
			}

			data, _ := json.MarshalIndent(resp, "", "  ")
			return TextResponse(string(data)), nil
		})
}

func NewDelegateMultiTool(registry *Registry) (*ToolDefinition, error) {
	return NewTool("delegate_multi",
		"Execute multiple independent tasks in parallel using goroutines. Each task runs in its own isolated context. Returns aggregated results. Use for batch operations, parallel analysis, or concurrent file processing.",
		func(ctx context.Context, p DelegateMultiParams) (ToolResponse, error) {
			if len(p.Tasks) == 0 {
				return TextErrorResponse("tasks list is required"), nil
			}

			maxParallel := p.MaxParallel
			if maxParallel <= 0 {
				maxParallel = 3
			}
			if maxParallel > 5 {
				maxParallel = 5
			}

			if len(p.Tasks) > 10 {
				return TextErrorResponse("maximum 10 tasks per delegation"), nil
			}

			start := time.Now()
			results := executeParallelTasks(ctx, p.Tasks, maxParallel, registry)
			duration := time.Since(start)

			resp := DelegateResponse{
				TotalTasks: len(results),
				Results:    results,
				Duration:   duration.Round(time.Millisecond).String(),
			}

			for _, r := range results {
				if r.Success {
					resp.Successful++
				} else {
					resp.Failed++
				}
			}

			data, _ := json.MarshalIndent(resp, "", "  ")
			return TextResponse(string(data)), nil
		})
}

func executeSubTask(ctx context.Context, task, contextInfo string, timeout time.Duration, registry *Registry) SubTaskResult {
	taskCtx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	result := SubTaskResult{
		Task: truncateString(task, 100),
	}

	if contextInfo != "" {
		result.Task = fmt.Sprintf("%s (context: %s)", result.Task, truncateString(contextInfo, 50))
	}

	done := make(chan struct{})
	go func() {
		defer close(done)

		terminalTool, ok := registry.Get("terminal")
		if !ok {
			result.Error = "terminal tool not available"
			return
		}

		args, _ := json.Marshal(TerminalParams{
			Command: task,
			Timeout: int(timeout.Seconds()),
		})

		resp, err := terminalTool.Execute(taskCtx, ToolCall{
			ID:       fmt.Sprintf("delegate_%d", time.Now().UnixNano()),
			Name:     "terminal",
			Arguments: args,
		})
		if err != nil {
			result.Error = err.Error()
			return
		}

		result.Result = resp.Content
		result.Success = !resp.IsError
		if resp.IsError {
			result.Error = resp.Content
		}
	}()

	select {
	case <-done:
		return result
	case <-taskCtx.Done():
		result.Error = fmt.Sprintf("sub-task timed out after %v", timeout)
		return result
	}
}

func executeParallelTasks(ctx context.Context, tasks []DelegateTaskParams, maxParallel int, registry *Registry) []SubTaskResult {
	results := make([]SubTaskResult, len(tasks))

	sem := make(chan struct{}, maxParallel)
	var wg sync.WaitGroup

	for i, task := range tasks {
		wg.Add(1)
		go func(idx int, t DelegateTaskParams) {
			defer wg.Done()

			sem <- struct{}{}
			defer func() { <-sem }()

			timeout := time.Duration(t.Timeout) * time.Second
			if timeout == 0 {
				timeout = 120 * time.Second
			}
			if timeout > 300*time.Second {
				timeout = 300 * time.Second
			}

			results[idx] = executeSubTask(ctx, t.Task, t.Context, timeout, registry)
		}(i, task)
	}

	wg.Wait()
	return results
}

func truncateString(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}
