package exec

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"strings"

	agentmodel "github.com/tidbcloud/AgentTeam/desktop/internal/agent"

	invopopjsonschema "github.com/invopop/jsonschema"
	orderedmap "github.com/wk8/go-ordered-map/v2"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agentview"
)

type agentListResult struct {
	Agents     []agentExecSnapshot `json:"agents"`
	NextCursor string              `json:"nextCursor,omitempty" jsonschema:"maxLength=512"`
	HasMore    bool                `json:"hasMore"`
}

var agentListSpec = actionSpec[agentListRequest, agentListResult]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "list",
		Summary:    "list agents in the current workspace",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[agentListRequest]{
		{Title: "all agents", Input: agentListRequest{}},
	},
}

type agentGetResult struct {
	Agent agentExecSnapshot `json:"agent"`
}

var agentGetSpec = actionSpec[agentGetRequest, agentGetResult]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "get",
		Summary:    "get detailed agent configuration",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[agentGetRequest]{
		{Title: "by id", Input: agentGetRequest{AgentID: "agt_123"}},
	},
}

type agentHandler struct {
	source   AgentReader
	remote   agentRuntime
	local    localReadTransport
	localGet localReadTransport
	receipt  requestReceiptTransport
}

// runtime returns the agent-management forwarding client for direct handler
// unit tests. Production invokers bind a fresh handler with only their owned
// receipt transport, so the remote seam is never captured by route assembly.
func (handler agentHandler) runtime(authorized runtimeAuthorization) agentRuntime {
	if handler.remote != nil {
		return handler.remote
	}
	return agentRemote{client: authorized.Server, receipt: handler.receipt}
}

func (handler agentHandler) list(ctx context.Context, request actionScopedJSON[agentListRequest]) (actionResult[agentListResult], error) {
	source := handler.source
	if source == nil {
		return actionResult[agentListResult]{}, NewError(CodeInternal, "agent source unavailable")
	}
	query := strings.ToLower(strings.TrimSpace(request.Body.Query))
	cursor, err := decodeAgentListCursor(request.Body.Cursor, query)
	if err != nil {
		return actionResult[agentListResult]{}, classifyAgentPageError(err)
	}
	limit := request.Body.Limit
	if limit <= 0 {
		limit = 20
	}
	if limit > 100 {
		return actionResult[agentListResult]{}, NewError(CodeValidation, "limit must be at most 100")
	}
	if paged, ok := source.(agentPageReaderWithAnchor); ok {
		items, hasMore, revision, nextAnchor, err := paged.ListAgentsPageWithAnchor(ctx, query, cursor.Revision, cursor.AgentID, limit)
		if err != nil {
			return actionResult[agentListResult]{}, classifyAgentPageError(err)
		}
		return renderAgentPage(items, hasMore, revision, nextAnchor, query)
	}
	if paged, ok := source.(agentPageReader); ok {
		items, hasMore, revision, err := paged.ListAgentsPage(ctx, query, cursor.Revision, cursor.AgentID, limit)
		if err != nil {
			return actionResult[agentListResult]{}, classifyAgentPageError(err)
		}
		anchor := ""
		if len(items) > 0 {
			anchor = items[len(items)-1].ID
		}
		return renderAgentPage(items, hasMore, revision, anchor, query)
	}
	responses, err := source.ListAgents(ctx)
	if err != nil {
		return actionResult[agentListResult]{}, &Error{Code: CodeInternal, Problem: "list agents failed", Cause: err}
	}
	items := make([]agentview.Snapshot, 0, len(responses))
	for _, response := range responses {
		if query == "" || strings.Contains(strings.ToLower(response.ID+" "+response.Name), query) {
			items = append(items, response)
		}
	}
	sort.SliceStable(items, func(i, j int) bool {
		if items[i].ID != items[j].ID {
			return items[i].ID < items[j].ID
		}
		return items[i].Name < items[j].Name
	})
	start := 0
	if cursor.AgentID != "" {
		for index := range items {
			if items[index].ID == cursor.AgentID {
				start = index + 1
				break
			}
		}
		if start == 0 {
			return actionResult[agentListResult]{}, NewError(CodeValidation, "agent list cursor is stale")
		}
	}
	if start > len(items) {
		start = len(items)
	}
	end := start + limit
	if end > len(items) {
		end = len(items)
	}
	next := ""
	if end < len(items) {
		revision := cursor.Revision
		if revision == "" {
			revision = "legacy"
		}
		next = encodeAgentListCursor(revision, query, items[end-1].ID)
	}
	projected := make([]agentExecSnapshot, 0, end-start)
	for _, item := range items[start:end] {
		projected = append(projected, projectAgentSnapshot(item))
	}
	return actionResult[agentListResult]{Data: agentListResult{Agents: projected, NextCursor: next, HasMore: next != ""}, Text: "Listed agents."}, nil
}

func renderAgentPage(items []agentview.Snapshot, hasMore bool, revision, nextAnchor, query string) (actionResult[agentListResult], error) {
	if strings.TrimSpace(revision) == "" {
		return actionResult[agentListResult]{}, NewError(CodeInternal, "agent list revision unavailable")
	}
	if hasMore && strings.TrimSpace(nextAnchor) == "" {
		return actionResult[agentListResult]{}, NewError(CodeInternal, "agent list continuation anchor unavailable")
	}
	projected := make([]agentExecSnapshot, 0, len(items))
	for _, item := range items {
		projected = append(projected, projectAgentSnapshot(item))
	}
	next := ""
	if hasMore {
		next = encodeAgentListCursor(revision, query, nextAnchor)
	}
	return actionResult[agentListResult]{
		Data: agentListResult{Agents: projected, NextCursor: next, HasMore: hasMore},
		Text: "Listed agents.",
	}, nil
}

func classifyAgentPageError(err error) *Error {
	var pageErr *agentmodel.PageError
	if !errors.As(err, &pageErr) || pageErr == nil {
		return &Error{Code: CodeInternal, Problem: "list agents failed", Cause: err}
	}
	switch pageErr.Kind {
	case agentmodel.PageErrorMalformed, agentmodel.PageErrorQueryMismatch, agentmodel.PageErrorLimit:
		return &Error{Code: CodeValidation, Problem: "invalid agent list page", Cause: err}
	case agentmodel.PageErrorStaleRevision, agentmodel.PageErrorStaleAnchor:
		return &Error{Code: CodeConflict, Problem: "agent list cursor is stale", Cause: err}
	default:
		return &Error{Code: CodeInternal, Problem: "list agents failed", Cause: err}
	}
}

func (handler agentHandler) get(ctx context.Context, request actionScopedJSON[agentGetRequest]) (actionResult[agentGetResult], error) {
	source := handler.source
	if source == nil {
		return actionResult[agentGetResult]{}, NewError(CodeInternal, "agent source unavailable")
	}
	id := strings.TrimSpace(request.Body.AgentID)
	response, ok, err := source.GetAgent(ctx, id)
	if err != nil {
		return actionResult[agentGetResult]{}, &Error{Code: CodeInternal, Problem: "get agent failed", Cause: err}
	}
	if !ok {
		return actionResult[agentGetResult]{}, NewError(CodeNotFound, fmt.Sprintf("agent %q not found", id))
	}
	return actionResult[agentGetResult]{Data: agentGetResult{Agent: projectAgentSnapshot(response)}, Text: "Fetched agent."}, nil
}

type agentListRequest struct {
	Query  string `json:"query,omitempty" jsonschema:"maxLength=256"`
	Limit  int    `json:"limit,omitempty" jsonschema:"minimum=1,maximum=100"`
	Cursor string `json:"cursor,omitempty" jsonschema:"maxLength=512"`
}
type agentGetRequest struct {
	AgentID string `json:"agentId" jsonschema:"minLength=1,pattern=.*\\S.*"`
}

type agentLifecycleRequest struct {
	RequestID string `json:"requestId" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
	AgentID   string `json:"agentId" jsonschema:"minLength=1,pattern=.*\\S.*"`
}

type agentLifecycleResponse struct {
	Status string `json:"status"`
}

type agentStartResponse struct {
	Status string `json:"status" jsonschema:"enum=started,enum=already_started"`
}

type agentStopOutcome struct {
	Status string `json:"status" jsonschema:"enum=stopping,enum=already_stopped"`
}

type agentRestartResponse struct {
	Status string `json:"status" jsonschema:"enum=restarted"`
}

type agentCreateRequest struct {
	RequestID       string   `json:"requestId" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
	Name            string   `json:"name" jsonschema:"minLength=1,pattern=.*\\S.*"`
	MachineID       string   `json:"machineId" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Runtime         string   `json:"runtime" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Description     string   `json:"description,omitempty"`
	Model           string   `json:"model,omitempty"`
	ReasoningEffort string   `json:"reasoningEffort,omitempty"`
	ModelProfileID  string   `json:"modelProfileId,omitempty"`
	ChannelIDs      []string `json:"channelIds,omitempty"`
}

type agentUpdateRequest struct {
	RequestID       string              `json:"requestId" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
	AgentID         string              `json:"agentId" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Description     agentOptionalString `json:"description,omitempty,omitzero"`
	Model           agentOptionalString `json:"model,omitempty,omitzero"`
	ReasoningEffort agentOptionalString `json:"reasoningEffort,omitempty,omitzero"`
	ModelProfileID  agentOptionalString `json:"modelProfileId,omitempty,omitzero"`
}

func (agentUpdateRequest) JSONSchema() *invopopjsonschema.Schema {
	properties := orderedmap.New[string, *invopopjsonschema.Schema]()
	properties.Set("requestId", nonBlankAgentStringSchema(128))
	properties.Set("agentId", nonBlankAgentStringSchema(0))
	properties.Set("description", agentOptionalString{}.JSONSchema())
	properties.Set("model", agentOptionalString{}.JSONSchema())
	properties.Set("reasoningEffort", agentOptionalString{}.JSONSchema())
	properties.Set("modelProfileId", agentOptionalString{}.JSONSchema())
	changeRequired := make([]*invopopjsonschema.Schema, 0, 4)
	for _, field := range []string{"description", "model", "reasoningEffort", "modelProfileId"} {
		changeRequired = append(changeRequired, &invopopjsonschema.Schema{Required: []string{field}})
	}
	return &invopopjsonschema.Schema{
		Type:                 "object",
		Properties:           properties,
		Required:             []string{"requestId", "agentId"},
		AnyOf:                changeRequired,
		AdditionalProperties: invopopjsonschema.FalseSchema,
	}
}

func nonBlankAgentStringSchema(maxLength uint64) *invopopjsonschema.Schema {
	minLength := uint64(1)
	schema := &invopopjsonschema.Schema{Type: "string", MinLength: &minLength, Pattern: `.*\S.*`}
	if maxLength > 0 {
		schema.MaxLength = &maxLength
	}
	return schema
}

type agentDeleteRequest struct {
	RequestID string   `json:"requestId" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
	AgentIDs  []string `json:"agentIds" jsonschema:"minItems=1,maxItems=10"`
}

func (agentDeleteRequest) JSONSchema() *invopopjsonschema.Schema {
	properties := orderedmap.New[string, *invopopjsonschema.Schema]()
	properties.Set("requestId", nonBlankAgentStringSchema(128))
	minItems, maxItems := uint64(1), uint64(10)
	properties.Set("agentIds", &invopopjsonschema.Schema{
		Type: "array", MinItems: &minItems, MaxItems: &maxItems,
		Items: nonBlankAgentStringSchema(0),
	})
	return &invopopjsonschema.Schema{
		Type:                 "object",
		Properties:           properties,
		Required:             []string{"requestId", "agentIds"},
		AdditionalProperties: invopopjsonschema.FalseSchema,
	}
}

type agentDuplicateRequest struct {
	RequestID     string   `json:"requestId" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
	SourceAgentID string   `json:"sourceAgentId" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Count         *int     `json:"count,omitempty" jsonschema:"minimum=1,maximum=10"`
	Mode          string   `json:"mode,omitempty" jsonschema:"enum=config_only,enum=with_resources"`
	MachineID     string   `json:"machineId,omitempty"`
	Runtime       string   `json:"runtime,omitempty"`
	ChannelIDs    []string `json:"channelIds,omitempty"`
}

type agentStructuralResponse struct {
	Status     string `json:"status" jsonschema:"enum=pending"`
	ApprovalID string `json:"approvalId" jsonschema:"minLength=1,pattern=.*\\S.*"`
}

var agentStartSpec = actionSpec[agentLifecycleRequest, agentStartResponse]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "start",
		Summary:    "start an Agent owned by the same Human (enables auto-wake)",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[agentLifecycleRequest]{
		{Title: "start agent", Input: agentLifecycleRequest{RequestID: "req-start-01", AgentID: "agt_123"}},
	},
}

var agentStopSpec = actionSpec[agentLifecycleRequest, agentStopOutcome]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "stop",
		Summary:    "stop an Agent owned by the same Human (disables auto-wake)",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[agentLifecycleRequest]{
		{Title: "stop agent", Input: agentLifecycleRequest{RequestID: "req-stop-01", AgentID: "agt_123"}},
	},
}

var agentRestartSpec = actionSpec[agentLifecycleRequest, agentRestartResponse]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "restart",
		Summary:    "restart a running Agent owned by the same Human",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[agentLifecycleRequest]{
		{Title: "restart agent", Input: agentLifecycleRequest{RequestID: "req-restart-01", AgentID: "agt_123"}},
	},
}

var agentCreateSpec = actionSpec[agentCreateRequest, agentStructuralResponse]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "create",
		Summary:    "request approval to create an Agent owned by the same Human",
		Permission: permissionExistingGate,
	},
	Examples: []actionExample[agentCreateRequest]{
		{Title: "create agent", Input: agentCreateRequest{
			RequestID: "req-create-01", Name: "researcher", MachineID: "mac_123", Runtime: "codex",
		}},
	},
}

var agentUpdateSpec = actionSpec[agentUpdateRequest, agentStructuralResponse]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "update",
		Summary:    "request approval to update an Agent owned by the same Human",
		Permission: permissionExistingGate,
	},
	Examples: []actionExample[agentUpdateRequest]{
		{Title: "update agent", Input: agentUpdateRequest{
			RequestID: "req-update-01", AgentID: "agt_123",
			Description: agentOptionalString{Set: true, Value: agentString("research tasks")},
		}},
	},
}

var agentDeleteSpec = actionSpec[agentDeleteRequest, agentStructuralResponse]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "delete",
		Summary:    "request approval to delete 1-10 Agents owned by the same Human",
		Permission: permissionExistingGate,
	},
	Examples: []actionExample[agentDeleteRequest]{
		{Title: "delete agent", Input: agentDeleteRequest{RequestID: "req-delete-01", AgentIDs: []string{"agt_123"}}},
	},
}

var agentDuplicateSpec = actionSpec[agentDuplicateRequest, agentStructuralResponse]{
	Meta: actionMeta{
		Domain:     "agent",
		Name:       "duplicate",
		Summary:    "request approval to duplicate an Agent owned by the same Human",
		Permission: permissionExistingGate,
	},
	Examples: []actionExample[agentDuplicateRequest]{
		{Title: "duplicate agent", Input: agentDuplicateRequest{
			RequestID: "req-duplicate-01", SourceAgentID: "agt_123",
		}},
	},
}

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

func (handler agentHandler) start(ctx context.Context, request actionScopedJSON[agentLifecycleRequest]) (actionResult[agentStartResponse], error) {
	response, err := handler.runtime(request.Authorized).lifecycle(ctx, "start", request.Body)
	if err != nil {
		return actionResult[agentStartResponse]{}, classifyAPIError(err, "start agent failed")
	}
	return actionResult[agentStartResponse]{Data: agentStartResponse{Status: response.Status}, Text: "Agent start requested."}, nil
}

func (handler agentHandler) stop(ctx context.Context, request actionScopedJSON[agentLifecycleRequest]) (actionResult[agentStopOutcome], error) {
	response, err := handler.runtime(request.Authorized).lifecycle(ctx, "stop", request.Body)
	if err != nil {
		return actionResult[agentStopOutcome]{}, classifyAPIError(err, "stop agent failed")
	}
	return actionResult[agentStopOutcome]{Data: agentStopOutcome{Status: response.Status}, Text: "Agent stop requested."}, nil
}

func (handler agentHandler) restart(ctx context.Context, request actionScopedJSON[agentLifecycleRequest]) (actionResult[agentRestartResponse], error) {
	response, err := handler.runtime(request.Authorized).lifecycle(ctx, "restart", request.Body)
	if err != nil {
		return actionResult[agentRestartResponse]{}, classifyAPIError(err, "restart agent failed")
	}
	return actionResult[agentRestartResponse]{Data: agentRestartResponse{Status: response.Status}, Text: "Agent restart requested."}, nil
}

func (handler agentHandler) create(ctx context.Context, request actionScopedJSON[agentCreateRequest]) (actionResult[agentStructuralResponse], error) {
	response, err := handler.runtime(request.Authorized).create(ctx, request.Body)
	if err != nil {
		return actionResult[agentStructuralResponse]{}, classifyAPIError(err, "create agent failed")
	}
	return actionResult[agentStructuralResponse]{Data: response, Text: "Agent creation submitted for approval."}, nil
}

func (handler agentHandler) update(ctx context.Context, request actionScopedJSON[agentUpdateRequest]) (actionResult[agentStructuralResponse], error) {
	response, err := handler.runtime(request.Authorized).update(ctx, request.Body)
	if err != nil {
		return actionResult[agentStructuralResponse]{}, classifyAPIError(err, "update agent failed")
	}
	return actionResult[agentStructuralResponse]{Data: response, Text: "Agent update submitted for approval."}, nil
}

func (handler agentHandler) delete(ctx context.Context, request actionScopedJSON[agentDeleteRequest]) (actionResult[agentStructuralResponse], error) {
	response, err := handler.runtime(request.Authorized).delete(ctx, request.Body)
	if err != nil {
		return actionResult[agentStructuralResponse]{}, classifyAPIError(err, "delete agent failed")
	}
	return actionResult[agentStructuralResponse]{Data: response, Text: "Agent deletion submitted for approval."}, nil
}

func (handler agentHandler) duplicate(ctx context.Context, request actionScopedJSON[agentDuplicateRequest]) (actionResult[agentStructuralResponse], error) {
	response, err := handler.runtime(request.Authorized).duplicate(ctx, request.Body)
	if err != nil {
		return actionResult[agentStructuralResponse]{}, classifyAPIError(err, "duplicate agent failed")
	}
	return actionResult[agentStructuralResponse]{Data: response, Text: "Agent duplication submitted for approval."}, nil
}
