package searchengine

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

	"github.com/tidbcloud/AgentTeam/internal/extension/providers/searchengine/runtime"
	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/service/extensionflow"
)

// Invoker is the provider-neutral service boundary used by Builtin executors.
type Invoker interface {
	Search(
		context.Context,
		runtime.SearchRequest,
	) (runtime.SearchResult, error)
}

// Executor fixes one provider identity at registration time.
type Executor struct {
	provider string
	invoker  Invoker
}

// NewExecutor binds a provider to the shared search service.
func NewExecutor(provider string, invoker Invoker) *Executor {
	return &Executor{provider: provider, invoker: invoker}
}

func (*Executor) Strategy() string {
	return "native"
}

// Invoke adapts the Builtin envelope to the shared normalized search service.
func (executor *Executor) Invoke(
	ctx context.Context,
	invocation registry.BuiltinInvocation,
) (registry.BuiltinResult, error) {
	if executor == nil || executor.invoker == nil {
		return registry.BuiltinResult{}, fmt.Errorf(
			"search Builtin executor is not configured",
		)
	}
	if invocation.Action != ActionSearch {
		return registry.BuiltinResult{}, &extensionflow.InvocationError{
			Code:    extensionflow.InvocationNotFound,
			Message: "search action was not found",
		}
	}
	var input struct {
		Query string `json:"query"`
		Limit int    `json:"limit"`
	}
	if err := json.Unmarshal(invocation.Input, &input); err != nil {
		return registry.BuiltinResult{}, &extensionflow.InvocationError{
			Code:    extensionflow.InvocationValidation,
			Message: "search input was invalid",
		}
	}
	result, err := executor.invoker.Search(ctx, runtime.SearchRequest{
		WorkspaceID:           invocation.WorkspaceID,
		AgentID:               invocation.AgentID,
		AgentMemberID:         invocation.AgentMemberID,
		ServiceInstallationID: invocation.ServiceInstallationID,
		Provider:              executor.provider,
		Query:                 input.Query,
		Limit:                 input.Limit,
	})
	if err != nil {
		return registry.BuiltinResult{}, classifySearchError(err)
	}
	encoded, err := json.Marshal(result)
	if err != nil {
		return registry.BuiltinResult{}, fmt.Errorf(
			"encode search result: %w",
			err,
		)
	}
	return registry.BuiltinResult{Data: encoded}, nil
}

func classifySearchError(err error) error {
	if err == nil {
		return nil
	}
	var code extensionflow.InvocationErrorCode
	var message string
	switch runtime.CodeOf(err) {
	case runtime.ErrorValidation:
		code = extensionflow.InvocationValidation
		message = "search input was invalid"
	case runtime.ErrorProviderAuthUnavailable:
		code = extensionflow.InvocationProviderAuthUnavailable
		message = "search provider authentication is unavailable"
	case runtime.ErrorRateLimited:
		code = extensionflow.InvocationRateLimited
		message = "search provider rate limited the request"
	case runtime.ErrorUpstreamFailure:
		code = extensionflow.InvocationUpstreamFailure
		message = "search provider request failed"
	default:
		return err
	}
	return &extensionflow.InvocationError{
		Code:    code,
		Message: message,
	}
}
