package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"chat-switchboard/models"
	"chat-switchboard/store"
	"chat-switchboard/taskutil"
)

// ── Late Registration ────────────────────────

// RegisterTaskTools registers the task_create tool.
// Called from main.go after stores are initialized.
func RegisterTaskTools(stores store.Stores) {
	if stores.Tasks == nil {
		log.Println("⚠ task tools: TaskStore not available, skipping registration")
		return
	}
	Register(&taskCreateTool{stores: stores})
	log.Println("✅ task tools registered (task_create)")
}

// ═══════════════════════════════════════════
// task_create
// ═══════════════════════════════════════════
//
// AI-invocable tool that spawns sub-tasks. Only available inside
// workflow or team channels — prevents runaway task creation in
// personal chats. Tasks cannot create tasks (depth limit: service
// channels are blocked).

type taskCreateTool struct {
	BaseTool
	stores store.Stores
}

// Availability: workflow or team context only — not personal chats,
// not service channels (prevents recursive task spawning).
func (t *taskCreateTool) Availability() Require {
	return func(tc ToolContext) bool {
		// Block in service channels (task output) — prevents recursion
		if tc.ChannelType == "service" {
			return false
		}
		// Require workflow or team context
		return tc.WorkflowID != "" || tc.TeamID != ""
	}
}

func (t *taskCreateTool) Definition() ToolDef {
	return ToolDef{
		Name:        "task_create",
		DisplayName: "Create Task",
		Category:    "automation",
		Description: "Create a new scheduled or one-shot task. Use this to spawn " +
			"background work that runs independently — for example, a follow-up " +
			"analysis, a periodic check, or a notification workflow. The task runs " +
			"in its own service channel with its own budget.",
		Parameters: JSONSchema(map[string]interface{}{
			"name": Prop("string", "Short descriptive name for the task"),
			"prompt": Prop("string", "The user prompt the task will execute"),
			"schedule": Prop("string",
				"Cron expression (e.g. '0 8 * * *' for daily at 8am) or 'once' for immediate one-shot execution"),
			"persona": Prop("string",
				"Persona handle to use for execution (optional — uses default if omitted)"),
			"model": Prop("string",
				"Model ID to use (optional — uses persona or config default)"),
			"max_tokens": map[string]interface{}{
				"type":        "integer",
				"description": "Maximum tokens for the task (optional — uses global default)",
			},
			"system_prompt": Prop("string",
				"Optional system prompt override for the task"),
		}, []string{"name", "prompt", "schedule"}),
	}
}

func (t *taskCreateTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Name         string `json:"name"`
		Prompt       string `json:"prompt"`
		Schedule     string `json:"schedule"`
		Persona      string `json:"persona"`
		Model        string `json:"model"`
		MaxTokens    int    `json:"max_tokens"`
		SystemPrompt string `json:"system_prompt"`
	}
	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
		return "", fmt.Errorf("invalid arguments: %w", err)
	}

	if args.Name == "" || args.Prompt == "" || args.Schedule == "" {
		return "", fmt.Errorf("name, prompt, and schedule are required")
	}

	// F5 audit fix: reject webhook schedule — webhook-triggered tasks
	// require API creation (trigger token generation, no cron).
	if args.Schedule == "webhook" {
		return "", fmt.Errorf("webhook-triggered tasks cannot be created via this tool — use the API directly")
	}

	// Validate cron
	if err := taskutil.ValidateCron(args.Schedule); err != nil {
		return "", fmt.Errorf("invalid schedule: %w", err)
	}

	// Depth guard: verify we're not inside a service channel (task output)
	if execCtx.ChannelID != "" {
		channelType, _, _ := t.stores.Channels.GetTypeAndTeamID(ctx, execCtx.ChannelID)
		if channelType == "service" {
			return "", fmt.Errorf("tasks cannot create tasks (depth limit)")
		}
	}

	// Resolve persona ID from handle
	var personaID *string
	if args.Persona != "" {
		pID, err := t.stores.Personas.FindActiveByHandle(ctx, args.Persona)
		if err == nil && pID != "" {
			personaID = &pID
		}
	}

	// Inherit scope from channel context
	scope := "personal"
	var teamID *string
	if execCtx.TeamID != "" {
		scope = "team"
		teamID = &execCtx.TeamID
	}

	// Build task
	task := &models.Task{
		OwnerID:      execCtx.UserID,
		TeamID:       teamID,
		Name:         args.Name,
		Scope:        scope,
		TaskType:     "prompt",
		PersonaID:    personaID,
		ModelID:      args.Model,
		SystemPrompt: args.SystemPrompt,
		UserPrompt:   args.Prompt,
		Schedule:     args.Schedule,
		Timezone:     "UTC",
		IsActive:     true,
		OutputMode:   "channel",
		NotifyOnFailure: true,
	}

	if args.MaxTokens > 0 {
		task.MaxTokens = args.MaxTokens
	}

	// Apply global defaults for zero-value budgets
	cfg := taskutil.LoadTaskConfig(ctx, t.stores.GlobalConfig)
	cfg.ApplyDefaults(task)

	// Compute next_run_at
	if args.Schedule == "once" {
		now := time.Now().UTC()
		task.NextRunAt = &now
	} else {
		task.NextRunAt = taskutil.NextRunFromSchedule(args.Schedule, "UTC")
	}

	if err := t.stores.Tasks.Create(ctx, task); err != nil {
		return "", fmt.Errorf("failed to create task: %w", err)
	}

	log.Printf("[task_create] AI spawned task %s (%s) schedule=%s scope=%s",
		task.ID, task.Name, args.Schedule, scope)

	result, _ := json.Marshal(map[string]interface{}{
		"task_id":     task.ID,
		"name":        task.Name,
		"schedule":    args.Schedule,
		"scope":       scope,
		"next_run_at": task.NextRunAt,
		"message":     fmt.Sprintf("Task '%s' created successfully. It will run %s.", task.Name, scheduleDesc(args.Schedule)),
	})
	return string(result), nil
}

func scheduleDesc(schedule string) string {
	if schedule == "once" {
		return "immediately (one-shot)"
	}
	return "on schedule: " + schedule
}
