package exec

import (
	"context"
	"strings"

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

type reviewListResult struct {
	Contexts   []reviewContext `json:"contexts"`
	HasMore    bool            `json:"hasMore"`
	NextCursor string          `json:"nextCursor,omitempty"`
}

var reviewListSpec = actionSpec[reviewListRequest, reviewListResult]{
	Meta: actionMeta{
		Domain:      "review",
		Name:        "list",
		Summary:     "list review contexts bound to a thread or task",
		Description: "List review contexts bound to one explicit reply-thread or canonical Task scope.",
		Permission:  permissionReadOnly,
	},
	Examples: []actionExample[reviewListRequest]{
		{Title: "Task scope", Input: reviewListRequest{TaskScope: &reviewTaskScope{Kind: "task", ScopeID: "tsk_123"}}},
	},
}

type reviewAddResult struct {
	Context reviewContext `json:"context"`
}

var reviewAddSpec = actionSpec[reviewAddInput, reviewAddResult]{
	Meta: actionMeta{
		Domain:      "review",
		Name:        "add",
		Summary:     "attach a git review context to a thread or task",
		Description: "Add a git review context to one explicit reply-thread or canonical Task scope.",
		Permission:  permissionAgentScopedMutation,
	},
	Examples: []actionExample[reviewAddInput]{
		{Title: "add to Task", Input: reviewAddInput{Path: "/absolute/path/repo", TaskScope: &reviewTaskScope{Kind: "task", ScopeID: "tsk_123"}}},
	},
}

type reviewDeleteResult struct {
	ContextID string `json:"contextId"`
}

var reviewDeleteSpec = actionSpec[reviewDeleteRequest, reviewDeleteResult]{
	Meta: actionMeta{
		Domain:      "review",
		Name:        "delete",
		Summary:     "delete a review context by contextId",
		Description: "Delete a single review context by its contextId.",
		Permission:  permissionAgentScopedMutation,
	},
	Examples: []actionExample[reviewDeleteRequest]{
		{Title: "delete review", Input: reviewDeleteRequest{ContextID: "rcx_1"}},
	},
}

type reviewHandler struct {
	read    remoteReadTransport
	natural naturalMutationTransport
}

func (r reviewHandler) add(ctx context.Context, request actionScopedJSON[reviewAddInput]) (actionResult[reviewAddResult], error) {
	path := strings.TrimSpace(request.Body.Path)
	if path == "" {
		return actionResult[reviewAddResult]{}, NewError(CodeValidation, "path is required")
	}
	scope, err := reviewScopeFromInput(request.Body.Thread, request.Body.TaskScope)
	if err != nil {
		return actionResult[reviewAddResult]{}, err
	}
	review, err := (reviewRemote{client: request.Authorized.Server, read: r.read, natural: r.natural}).add(
		ctx,
		scope,
		reviewAddRequest{
			Path: path, Base: request.Body.Base, Head: request.Body.Head,
			Name: request.Body.Name, PRURL: request.Body.PRURL,
		},
	)
	if err != nil {
		return actionResult[reviewAddResult]{}, classifyAPIError(err, "add review context failed")
	}
	return actionResult[reviewAddResult]{
		Data: reviewAddResult{Context: review},
		Text: "Added review context: " + review.ID,
	}, nil
}

func (r reviewHandler) list(ctx context.Context, request actionScopedJSON[reviewListRequest]) (actionResult[reviewListResult], error) {
	scope, err := reviewScopeFromInput(request.Body.Thread, request.Body.TaskScope)
	if err != nil {
		return actionResult[reviewListResult]{}, err
	}
	input := request.Body
	if input.Limit <= 0 {
		input.Limit = 20
	}
	if input.Limit > 100 {
		input.Limit = 100
	}
	input.Cursor = strings.TrimSpace(input.Cursor)
	remote := reviewRemote{client: request.Authorized.Server, read: r.read, natural: r.natural}
	var contexts reviewListTransportResult
	if scope.taskScopeID != "" {
		items, listErr := remote.list(ctx, scope)
		if listErr != nil {
			return actionResult[reviewListResult]{}, classifyAPIError(listErr, "list review contexts failed")
		}
		contexts.Contexts = items
	} else {
		contexts, err = remote.listPage(ctx, scope.threadTarget, input)
		if err != nil {
			return actionResult[reviewListResult]{}, classifyAPIError(err, "list review contexts failed")
		}
	}
	if contexts.Contexts == nil {
		contexts.Contexts = []reviewContext{}
	}
	return actionResult[reviewListResult]{
		Data: reviewListResult{Contexts: contexts.Contexts, HasMore: contexts.HasMore, NextCursor: contexts.NextCursor},
		Text: reviewListSummary(contexts.Contexts),
	}, nil
}

func (r reviewHandler) delete(ctx context.Context, request actionScopedJSON[reviewDeleteRequest]) (actionResult[reviewDeleteResult], error) {
	id := strings.TrimSpace(request.Body.ContextID)
	if id == "" {
		return actionResult[reviewDeleteResult]{}, NewError(CodeValidation, "contextId is required")
	}
	if err := (reviewRemote{client: request.Authorized.Server, read: r.read, natural: r.natural}).delete(ctx, id); err != nil {
		return actionResult[reviewDeleteResult]{}, classifyAPIError(err, "delete review context failed")
	}
	return actionResult[reviewDeleteResult]{
		Data: reviewDeleteResult{ContextID: id},
		Text: "Deleted review context: " + id,
	}, nil
}

func reviewThreadTarget(value string) (string, error) {
	target := strings.TrimSpace(value)
	if target == "" {
		return "", NewError(CodeValidation, "thread target is required")
	}
	if isBareThreadShortID(target) {
		return "", NewError(CodeValidation, "bare thread short-id must be resolved by invocation binding")
	}
	if !strings.Contains(target, "#") && !strings.HasPrefix(target, "dm:") {
		return "", NewError(CodeValidation, "thread target must be a full thread reference like #channel:threadid or dm:@user:threadid")
	}
	return target, nil
}

func isBareThreadShortID(value string) bool {
	if len(value) < 6 || len(value) > 8 || strings.ContainsAny(value, "#:/@ ") {
		return false
	}
	for _, char := range value {
		if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f')) {
			return false
		}
	}
	return true
}

func threadShortIDSuffixMatches(shortID, target string) bool {
	index := strings.LastIndex(target, ":")
	return index >= 0 && index < len(target)-1 && target[index+1:] == shortID
}

type reviewTaskScope struct {
	Kind    string `json:"kind" jsonschema:"enum=task"`
	ScopeID string `json:"scopeId" jsonschema:"minLength=1,pattern=^tsk_.*$"`
}

type reviewAddInput struct {
	Path      string           `json:"path" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Base      string           `json:"base,omitempty"`
	Head      string           `json:"head,omitempty"`
	Thread    string           `json:"thread,omitempty"`
	TaskScope *reviewTaskScope `json:"taskScope,omitempty"`
	Name      string           `json:"name,omitempty"`
	PRURL     string           `json:"prUrl,omitempty"`
}
type reviewListRequest struct {
	Thread    string           `json:"thread,omitempty"`
	TaskScope *reviewTaskScope `json:"taskScope,omitempty"`
	Limit     int              `json:"limit,omitempty" jsonschema:"minimum=1,maximum=100"`
	Cursor    string           `json:"cursor,omitempty" jsonschema:"maxLength=512"`
}

type reviewAddThreadInput struct {
	Path   string `json:"path" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Base   string `json:"base,omitempty"`
	Head   string `json:"head,omitempty"`
	Thread string `json:"thread" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Name   string `json:"name,omitempty"`
	PRURL  string `json:"prUrl,omitempty"`
}
type reviewAddTaskInput struct {
	Path      string          `json:"path" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Base      string          `json:"base,omitempty"`
	Head      string          `json:"head,omitempty"`
	TaskScope reviewTaskScope `json:"taskScope"`
	Name      string          `json:"name,omitempty"`
	PRURL     string          `json:"prUrl,omitempty"`
}

type reviewListThreadInput struct {
	Thread string `json:"thread" jsonschema:"minLength=1,pattern=.*\\S.*"`
	Limit  int    `json:"limit,omitempty" jsonschema:"minimum=1,maximum=100"`
	Cursor string `json:"cursor,omitempty" jsonschema:"maxLength=512"`
}

type reviewListTaskInput struct {
	TaskScope reviewTaskScope `json:"taskScope"`
}

func reviewScopeSchema(branches ...*invopopjsonschema.Schema) *invopopjsonschema.Schema {
	properties := orderedmap.New[string, *invopopjsonschema.Schema]()
	for _, branch := range branches {
		branch.Version = ""
		for pair := branch.Properties.Oldest(); pair != nil; pair = pair.Next() {
			properties.Set(pair.Key, pair.Value)
		}
		if taskScope, ok := branch.Properties.Get("taskScope"); ok {
			taskScope.AdditionalProperties = invopopjsonschema.FalseSchema
		}
		branch.Type = ""
		branch.Properties = nil
		branch.AdditionalProperties = nil
	}
	return &invopopjsonschema.Schema{
		Properties: properties, Type: "object", AdditionalProperties: invopopjsonschema.FalseSchema,
		OneOf: branches,
	}
}

func (reviewAddInput) JSONSchema() *invopopjsonschema.Schema {
	reflector := invopopjsonschema.Reflector{Anonymous: true, DoNotReference: true, ExpandedStruct: true}
	return reviewScopeSchema(reflector.Reflect(reviewAddThreadInput{}), reflector.Reflect(reviewAddTaskInput{}))
}

func (reviewListRequest) JSONSchema() *invopopjsonschema.Schema {
	reflector := invopopjsonschema.Reflector{Anonymous: true, DoNotReference: true, ExpandedStruct: true}
	return reviewScopeSchema(reflector.Reflect(reviewListThreadInput{}), reflector.Reflect(reviewListTaskInput{}))
}

func reviewScopeFromInput(thread string, taskScope *reviewTaskScope) (reviewScope, error) {
	thread = strings.TrimSpace(thread)
	if (thread == "") == (taskScope == nil) {
		return reviewScope{}, NewError(CodeValidation, "exactly one of thread or taskScope is required")
	}
	if taskScope != nil {
		if strings.TrimSpace(taskScope.Kind) != "task" || !strings.HasPrefix(strings.TrimSpace(taskScope.ScopeID), "tsk_") {
			return reviewScope{}, NewError(CodeValidation, "taskScope must be {kind:'task',scopeId:'tsk_...'}")
		}
		return reviewScope{taskScopeID: strings.TrimSpace(taskScope.ScopeID)}, nil
	}
	threadTarget, err := reviewThreadTarget(thread)
	if err != nil {
		return reviewScope{}, err
	}
	return reviewScope{threadTarget: threadTarget}, nil
}

type reviewDeleteRequest struct {
	ContextID string `json:"contextId" jsonschema:"minLength=1,pattern=.*\\S.*"`
}
