package jenkins

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

	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
)

type actionHandler func(
	context.Context,
	*targetClient,
	runner.Credentials,
	json.RawMessage,
) (json.RawMessage, error)

// Executor resolves the active App connection, obtains the guarded client for
// its canonical Jenkins URL, and dispatches the closed action table.
type Executor struct {
	credentials runner.CredentialSource
	clients     *clientCache
	handlers    map[string]actionHandler
}

func newExecutor(
	credentials runner.CredentialSource,
	clients *clientCache,
) *Executor {
	return &Executor{
		credentials: credentials,
		clients:     clients,
		handlers:    handlers(),
	}
}

func (executor *Executor) Strategy() string {
	return "http"
}

func (executor *Executor) ActionNames() []string {
	names := make([]string, 0, len(executor.handlers))
	for name := range executor.handlers {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}

func (executor *Executor) Invoke(
	ctx context.Context,
	invocation registry.BuiltinInvocation,
) (registry.BuiltinResult, error) {
	if executor == nil || executor.credentials == nil || executor.clients == nil {
		return registry.BuiltinResult{}, fmt.Errorf(
			"Jenkins executor is not configured")
	}
	handler, ok := executor.handlers[invocation.Action]
	if !ok {
		return registry.BuiltinResult{}, runner.Errorf(
			runner.ErrorNotFound,
			"Jenkins action is not served",
			nil,
		)
	}
	if len(invocation.Input) == 0 || !json.Valid(invocation.Input) {
		return registry.BuiltinResult{}, runner.Errorf(
			runner.ErrorValidation,
			"Jenkins action input was not valid JSON",
			nil,
		)
	}
	credentials, err := executor.credentials.ResolveProviderCredentials(
		ctx,
		invocation.WorkspaceID,
		invocation.ServiceInstallationID,
	)
	if err != nil {
		if mapped := runner.InvocationError(runner.CredentialsUnavailable(err)); mapped != nil {
			return registry.BuiltinResult{}, mapped
		}
		return registry.BuiltinResult{}, err
	}
	client, err := executor.resolveClient(credentials)
	if err != nil {
		return registry.BuiltinResult{}, runner.InvocationError(err)
	}
	output, err := handler(ctx, client, credentials, invocation.Input)
	if err != nil {
		if mapped := runner.InvocationError(err); mapped != nil {
			return registry.BuiltinResult{}, mapped
		}
		return registry.BuiltinResult{}, err
	}
	if len(output) == 0 {
		output = json.RawMessage("null")
	}
	return registry.BuiltinResult{Data: output}, nil
}

func (executor *Executor) resolveClient(
	credentials runner.Credentials,
) (*targetClient, error) {
	if executor == nil || executor.clients == nil {
		return nil, runner.Errorf(
			runner.ErrorConnectionUnavailable,
			"Jenkins client cache is unavailable",
			nil,
		)
	}
	if err := AuthSpec().Validate(credentials); err != nil {
		return nil, runner.Errorf(
			runner.ErrorConnectionUnavailable,
			"Jenkins connection credentials are invalid",
			err,
		)
	}
	client, err := executor.clients.resolve(credentials["baseUrl"])
	if err != nil {
		return nil, runner.Errorf(
			runner.ErrorConnectionUnavailable,
			"Jenkins URL is invalid",
			err,
		)
	}
	return client, nil
}
