package exec

import (
	"context"
	"strings"

	invopopjsonschema "github.com/invopop/jsonschema"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
)

type taskSummary struct {
	TaskScopeID     string            `json:"taskScopeId,omitempty"`
	TaskNumber      int64             `json:"taskNumber"`
	Title           string            `json:"title"`
	Status          string            `json:"status,omitempty"`
	AssigneeProfile *taskActorProfile `json:"assigneeProfile,omitempty"`
	Reused          bool              `json:"reused,omitempty"`
}

// taskActorProfile is the compact model-facing projection of the canonical
// Task assignee identity returned by the Agent runtime API. Visual decoration
// stays out of AgentTeam Exec, while stable identity remains available for deciding
// whether to claim, continue, or hand off a Task.
type taskActorProfile struct {
	ActorType   string `json:"actorType"`
	PrincipalID string `json:"principalId,omitempty"`
	MemberID    string `json:"memberId,omitempty"`
	TargetID    string `json:"targetId,omitempty"`
	DisplayName string `json:"displayName"`
}

type taskCreateResult struct {
	ChannelRef string        `json:"channelRef,omitempty"`
	Tasks      []taskSummary `json:"tasks"`
}

var taskCreateSpec = actionSpec[taskCreateRequest, taskCreateResult]{
	Meta: actionMeta{
		Domain:      "task",
		Name:        "create",
		Summary:     "create one rich task, title-only batch, or subtasks",
		Description: "Create a rich task, a title-only batch, or subtasks under a parent. Omit channel and target to default to the current channel or channel reply thread: a channel turn files to that board; a thread turn files to the parent board with the thread recorded as the task origin. Other turn kinds require an explicit channel.",
		Permission:  permissionAgentScopedMutation,
	},
	Examples: []actionExample[taskCreateRequest]{
		{Title: "current channel or channel reply thread", Input: taskCreateRequest{Tasks: []taskDraft{{Title: "a"}, {Title: "b"}}}},
	},
}

type taskDetail struct {
	Task               taskSummary         `json:"task"`
	ParentTask         *taskSummary        `json:"parentTask,omitempty"`
	AcceptanceCriteria []taskChecklistItem `json:"acceptanceCriteria,omitempty"`
	ReviewChecklist    []taskChecklistItem `json:"reviewChecklist,omitempty"`
	Subtasks           []taskSummary       `json:"subtasks,omitempty"`
	Messages           []string            `json:"messages,omitempty"`
	URLs               []string            `json:"urls,omitempty"`
	Workspaces         []string            `json:"workspaces,omitempty"`
}

type taskDetailResult struct {
	Detail taskDetail `json:"detail"`
}

var taskGetSpec = actionSpec[taskGetRequest, taskDetailResult]{
	Meta: actionMeta{
		Domain:     "task",
		Name:       "get",
		Summary:    "get compact detail for one task",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[taskGetRequest]{
		{Title: "by number", Input: taskGetRequest{Channel: "#agentteam", Number: 2021}},
	},
}

type taskListResult struct {
	ChannelRef string        `json:"channelRef,omitempty"`
	Tasks      []taskSummary `json:"tasks"`
	Limit      int           `json:"limit,omitempty"`
	TotalCount int64         `json:"totalCount,omitempty"`
	HasMore    bool          `json:"hasMore,omitempty"`
	NextCursor string        `json:"nextCursor,omitempty"`
}

var taskListSpec = actionSpec[taskListRequest, taskListResult]{
	Meta: actionMeta{
		Domain:     "task",
		Name:       "list",
		Summary:    "browse or search channel tasks",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[taskListRequest]{
		{Title: "active tasks", Input: taskListRequest{Channel: "#agentteam"}},
	},
}

type taskMutationResult struct {
	Task taskSummary `json:"task"`
}

var taskUpdateSpec = actionSpec[taskUpdateRequest, taskMutationResult]{
	Meta: actionMeta{
		Domain:     "task",
		Name:       "update",
		Summary:    "update task fields or status",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[taskUpdateRequest]{
		{Title: "set status", Input: taskUpdateRequest{Channel: "#agentteam", Number: 2021, Status: taskString("in_progress")}},
	},
}

type taskClaimFailure struct {
	TaskNumber int64  `json:"taskNumber"`
	Reason     string `json:"reason,omitempty"`
}

type taskClaimResult struct {
	Claimed int                `json:"claimed"`
	Failed  []taskClaimFailure `json:"failed,omitempty"`
}

var taskClaimSpec = actionSpec[taskClaimRequest, taskClaimResult]{
	Meta: actionMeta{
		Domain:     "task",
		Name:       "claim",
		Summary:    "claim tasks by current agent",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[taskClaimRequest]{
		{Title: "claim one", Input: taskClaimRequest{Channel: "#agentteam", Numbers: []int{2021}}},
	},
}

var taskUnclaimSpec = actionSpec[taskClaimRequest, taskClaimResult]{
	Meta: actionMeta{
		Domain:     "task",
		Name:       "unclaim",
		Summary:    "release task claims",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[taskClaimRequest]{
		{Title: "release one", Input: taskClaimRequest{Channel: "#agentteam", Numbers: []int{2021}}},
	},
}

var taskCheckpointSpec = actionSpec[taskCheckpointRequest, taskMutationResult]{
	Meta: actionMeta{
		Domain:     "task",
		Name:       "checkpoint",
		Summary:    "append a task progress checkpoint",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[taskCheckpointRequest]{
		{
			Title: "progress",
			Input: taskCheckpointRequest{Channel: "#agentteam", Number: 2021, Summary: "Implemented help renderer"},
		},
	},
}

type taskHandler struct {
	read        remoteReadTransport
	natural     naturalMutationTransport
	turnTargets TurnTargetReader
}

func (handler taskHandler) remote(authorized runtimeAuthorization) taskRemote {
	return taskRemote{client: authorized.Server, read: handler.read, natural: handler.natural}
}

func (handler taskHandler) checkpoint(ctx context.Context, request actionScopedJSON[taskCheckpointRequest]) (actionResult[taskMutationResult], error) {
	input := request.Body
	if err := validateTaskLocator(input.Channel, input.Number); err != nil {
		return actionResult[taskMutationResult]{}, err
	}
	if strings.TrimSpace(input.Summary) == "" {
		return actionResult[taskMutationResult]{}, NewError(CodeValidation, "summary is required")
	}
	result, err := handler.remote(request.Authorized).checkpoint(ctx, input)
	if err != nil {
		return actionResult[taskMutationResult]{}, classifyTaskError(err, "add task checkpoint failed")
	}
	return actionResult[taskMutationResult]{Data: result, Text: "Added task checkpoint."}, nil
}

func (handler taskHandler) claim(ctx context.Context, request actionScopedJSON[taskClaimRequest]) (actionResult[taskClaimResult], error) {
	return setTaskClaim(ctx, request, true, handler.read, handler.natural)
}

func (handler taskHandler) create(ctx context.Context, request actionScopedJSON[taskCreateRequest]) (actionResult[taskCreateResult], error) {
	input := request.Body
	input.Channel = strings.TrimSpace(input.Channel)
	input.Target = strings.TrimSpace(input.Target)
	if input.ParentTaskScope == nil && input.Channel == "" && input.Target == "" {
		if err := handler.applyCurrentTurnTaskCreateDefault(&input, request.Authorized.AgentID, request.Authorized.AuthorizationID); err != nil {
			return actionResult[taskCreateResult]{}, err
		}
	}
	if input.ParentTaskScope == nil && input.Channel == "" && input.Target == "" {
		return actionResult[taskCreateResult]{}, NewError(CodeValidation, "channel is required: pass channel (board) or target (thread) explicitly, or invoke from a channel/thread conversation")
	}
	if input.ParentTaskScope != nil {
		if input.Channel != "" || input.Parent != 0 {
			return actionResult[taskCreateResult]{}, NewError(CodeValidation, "parentTaskScope cannot be combined with channel or parent")
		}
		scope, err := domain.NormalizeMessageScopeValue(*input.ParentTaskScope)
		if err != nil || scope.Kind != domain.MessageScopeKindTask {
			return actionResult[taskCreateResult]{}, NewError(CodeValidation, "parentTaskScope must be a canonical Task scope")
		}
		input.ParentTaskScope = &scope
	}
	if len(input.Tasks) == 0 {
		return actionResult[taskCreateResult]{}, NewError(CodeValidation, "tasks must contain at least one task")
	}
	result, err := handler.remote(request.Authorized).create(ctx, input)
	if err != nil {
		return actionResult[taskCreateResult]{}, classifyTaskError(err, "create tasks failed")
	}
	if result.Tasks == nil {
		result.Tasks = []taskSummary{}
	}
	return actionResult[taskCreateResult]{Data: result, Text: "Created task(s)."}, nil
}

// applyCurrentTurnTaskCreateDefault fills the omitted channel/target pair from
// the daemon-authoritative target of the turn the caller is currently serving.
// The default is turn-scoped and re-read per request: a channel turn files the
// task on that channel's board; a channel reply-thread turn passes the thread
// ref as target so the server files the task on the parent board with the
// thread recorded as its origin. Every other turn context (DM, task
// discussion, Team, Workflow) fails closed and demands an explicit argument
// rather than guessing a board.
func (handler taskHandler) applyCurrentTurnTaskCreateDefault(input *taskCreateRequest, agentID, authorizationID string) error {
	if handler.turnTargets == nil {
		return nil
	}
	turnTarget := strings.TrimSpace(handler.turnTargets.CurrentTurnDeliveryTarget(agentID, authorizationID))
	if turnTarget == "" {
		return nil
	}
	parsed, err := domain.ParseMessageTargetReference(turnTarget)
	if err != nil {
		return nil
	}
	switch parsed.Kind {
	case domain.MessageTargetKindChannel:
		input.Channel = turnTarget
	case domain.MessageTargetKindReplyThread:
		if parsed.Thread.Type == domain.ThreadTypeDM {
			return NewError(CodeValidation, "channel is required: the current conversation is a direct message and task boards are channel-scoped; pass channel explicitly")
		}
		input.Target = turnTarget
	case domain.MessageTargetKindTask:
		return NewError(CodeValidation, "channel is required: the current conversation is a task discussion; pass channel explicitly, or use parentTaskScope to create a subtask of the current task")
	case domain.MessageTargetKindDM:
		return NewError(CodeValidation, "channel is required: the current conversation is a direct message and task boards are channel-scoped; pass channel explicitly")
	case domain.MessageTargetKindTeam, domain.MessageTargetKindWorkflow:
		return NewError(CodeValidation, "channel is required: the current conversation target does not map to a task board; pass channel or target explicitly")
	}
	return nil
}

func (handler taskHandler) get(ctx context.Context, request actionScopedJSON[taskGetRequest]) (actionResult[taskDetailResult], error) {
	if err := validateTaskLocator(request.Body.Channel, request.Body.Number); err != nil {
		return actionResult[taskDetailResult]{}, err
	}
	result, err := handler.remote(request.Authorized).get(ctx, request.Body)
	if err != nil {
		return actionResult[taskDetailResult]{}, classifyTaskError(err, "get task failed")
	}
	return actionResult[taskDetailResult]{Data: result, Text: "Fetched task."}, nil
}

func (handler taskHandler) list(ctx context.Context, request actionScopedJSON[taskListRequest]) (actionResult[taskListResult], error) {
	if strings.TrimSpace(request.Body.Channel) == "" {
		return actionResult[taskListResult]{}, NewError(CodeValidation, "channel is required")
	}
	result, err := handler.remote(request.Authorized).list(ctx, request.Body)
	if err != nil {
		return actionResult[taskListResult]{}, classifyTaskError(err, "list tasks failed")
	}
	if result.Tasks == nil {
		result.Tasks = []taskSummary{}
	}
	return actionResult[taskListResult]{Data: result, Text: "Listed tasks."}, nil
}

func (handler taskHandler) unclaim(ctx context.Context, request actionScopedJSON[taskClaimRequest]) (actionResult[taskClaimResult], error) {
	return setTaskClaim(ctx, request, false, handler.read, handler.natural)
}

func (handler taskHandler) update(ctx context.Context, request actionScopedJSON[taskUpdateRequest]) (actionResult[taskMutationResult], error) {
	if err := validateTaskLocator(request.Body.Channel, request.Body.Number); err != nil {
		return actionResult[taskMutationResult]{}, err
	}
	result, err := handler.remote(request.Authorized).update(ctx, request.Body)
	if err != nil {
		return actionResult[taskMutationResult]{}, classifyTaskError(err, "update task failed")
	}
	return actionResult[taskMutationResult]{Data: result, Text: "Updated task."}, nil
}

func setTaskClaim(ctx context.Context, request actionScopedJSON[taskClaimRequest], claimed bool, read remoteReadTransport, natural naturalMutationTransport) (actionResult[taskClaimResult], error) {
	input := request.Body
	if strings.TrimSpace(input.Channel) == "" || len(input.Numbers) == 0 {
		return actionResult[taskClaimResult]{}, NewError(CodeValidation, "channel and numbers are required")
	}
	for _, number := range input.Numbers {
		if number <= 0 {
			return actionResult[taskClaimResult]{}, NewError(CodeValidation, "numbers must contain only positive integers")
		}
	}
	result, err := (taskRemote{client: request.Authorized.Server, read: read, natural: natural}).setClaim(ctx, input, claimed)
	if err != nil {
		problem := "claim tasks failed"
		if !claimed {
			problem = "unclaim tasks failed"
		}
		return actionResult[taskClaimResult]{}, classifyTaskError(err, problem)
	}
	text := "Claimed task(s)."
	if !claimed {
		text = "Released task claim(s)."
	}
	return actionResult[taskClaimResult]{Data: result, Text: text}, nil
}

func validateTaskLocator(channel string, number int) error {
	if strings.TrimSpace(channel) == "" || number <= 0 {
		return NewError(CodeValidation, "channel and number are required")
	}
	return nil
}

type taskDraft struct {
	Title       string `json:"title" jsonschema:"minLength=1"`
	Description string `json:"description,omitempty"`
	Kind        string `json:"kind,omitempty"`
	Priority    string `json:"priority,omitempty"`
	Assignee    string `json:"assignee,omitempty"`
}
type taskCreateRequest struct {
	Channel         string                    `json:"channel,omitempty"`
	Tasks           []taskDraft               `json:"tasks" jsonschema:"minItems=1"`
	Parent          int                       `json:"parent,omitempty" jsonschema:"minimum=1"`
	ParentTaskScope *domain.MessageScopeValue `json:"parentTaskScope,omitempty"`
	Target          string                    `json:"target,omitempty"`
}
type taskGetRequest struct {
	Channel string `json:"channel,omitempty"`
	Number  int    `json:"number,omitempty" jsonschema:"minimum=1"`
}
type taskListRequest struct {
	Channel         string   `json:"channel,omitempty"`
	Query           string   `json:"query,omitempty"`
	Status          string   `json:"status,omitempty"`
	IncludeSubtasks bool     `json:"includeSubtasks,omitempty"`
	Limit           int      `json:"limit,omitempty" jsonschema:"minimum=1,maximum=100"`
	Cursor          string   `json:"cursor,omitempty"`
	Sort            string   `json:"sort,omitempty"`
	Filters         []string `json:"filters,omitempty"`
}
type taskUpdateRequest struct {
	Channel            string               `json:"channel,omitempty"`
	Number             int                  `json:"number,omitempty" jsonschema:"minimum=1"`
	Title              *string              `json:"title,omitempty"`
	Description        *string              `json:"description,omitempty"`
	Kind               *string              `json:"kind,omitempty"`
	Priority           *string              `json:"priority,omitempty"`
	Assignee           *string              `json:"assignee,omitempty"`
	AcceptanceCriteria *[]taskChecklistItem `json:"acceptanceCriteria,omitempty"`
	ReviewChecklist    *[]taskChecklistItem `json:"reviewChecklist,omitempty"`
	Messages           *[]string            `json:"messages,omitempty"`
	URLs               *[]string            `json:"urls,omitempty"`
	Workspaces         *[]string            `json:"workspaces,omitempty"`
	Attachments        *[]string            `json:"attachments,omitempty"`
	Blocker            *string              `json:"blocker,omitempty"`
	Parent             *string              `json:"parent,omitempty"`
	Status             *string              `json:"status,omitempty"`
}

func taskString(value string) *string { return &value }

type taskClaimRequest struct {
	Channel string `json:"channel,omitempty"`
	Numbers []int  `json:"numbers" jsonschema:"minItems=1"`
}
type taskCheckpointRequest struct {
	Channel string `json:"channel,omitempty"`
	Number  int    `json:"number,omitempty" jsonschema:"minimum=1"`
	Summary string `json:"summary" jsonschema:"minLength=1,maxLength=2048,pattern=.*\\S.*"`
}

type taskChecklistItem struct {
	ID      string `json:"id,omitempty"`
	Text    string `json:"text" jsonschema:"minLength=1"`
	Checked bool   `json:"checked,omitempty"`
}

func (taskClaimRequest) JSONSchema() *invopopjsonschema.Schema {
	return mustTypedWorkflowSchema(mustWorkflowSchema(closedWorkflowObject(
		map[string]any{
			"channel": map[string]any{"type": "string"},
			"numbers": map[string]any{
				"type": "array", "minItems": 1, "maxItems": 20,
				"items": map[string]any{"type": "integer", "minimum": 1},
			},
		},
		"channel", "numbers",
	)))
}

func (taskUpdateRequest) JSONSchema() *invopopjsonschema.Schema {
	properties := map[string]any{
		"channel":            map[string]any{"type": "string"},
		"number":             map[string]any{"type": "integer", "minimum": 1},
		"title":              taskNonBlankStringSchema(512),
		"description":        taskStringSchema(8192),
		"kind":               taskStringSchema(512),
		"priority":           taskStringSchema(512),
		"assignee":           taskStringSchema(512),
		"acceptanceCriteria": taskChecklistArraySchema(),
		"reviewChecklist":    taskChecklistArraySchema(),
		"messages":           stringArraySchema(),
		"urls":               stringArraySchema(),
		"workspaces":         stringArraySchema(),
		"attachments":        stringArraySchema(),
		"blocker":            taskStringSchema(512),
		"parent":             taskStringSchema(512),
		"status": map[string]any{
			"type": "string", "enum": []string{"todo", "in_progress", "in_review", "done"},
		},
	}
	changeNames := []string{
		"title", "description", "kind", "priority",
		"assignee", "acceptanceCriteria", "reviewChecklist",
		"messages", "urls", "workspaces", "attachments", "blocker",
		"parent",
	}
	detailChanges := make([]any, 0, len(changeNames))
	for _, name := range changeNames {
		detailChanges = append(detailChanges, map[string]any{"required": []string{name}})
	}
	schema := closedWorkflowObject(properties)
	schema["oneOf"] = []any{
		map[string]any{
			"required": []string{"status"},
			"not": map[string]any{
				"anyOf": detailChanges,
			},
		},
		map[string]any{
			"not":   map[string]any{"required": []string{"status"}},
			"anyOf": detailChanges,
		},
	}
	return mustTypedWorkflowSchema(mustWorkflowSchema(schema))
}

func taskChecklistArraySchema() map[string]any {
	return map[string]any{
		"type": "array", "maxItems": 50,
		"items": closedWorkflowObject(map[string]any{
			"id":      taskStringSchema(512),
			"text":    taskNonBlankStringSchema(2048),
			"checked": map[string]any{"type": "boolean"},
		}, "text"),
	}
}

func stringArraySchema() map[string]any {
	return map[string]any{
		"type": "array", "maxItems": 50,
		"items": taskStringSchema(1024),
	}
}

func taskNonBlankStringSchema(maxLength int) map[string]any {
	schema := taskStringSchema(maxLength)
	schema["pattern"] = `.*\S.*`
	return schema
}

func taskStringSchema(maxLength int) map[string]any {
	return map[string]any{"type": "string", "maxLength": maxLength}
}
