// Package dingtalkbot registers the DingTalk Bot Builtin Extension, ported
// from OpenConnector (baseline 0484cac): the custom robot webhook on
// oapi.dingtalk.com. The webhook access token rides the query string and the
// optional signing secret appends an HMAC-SHA256 timestamp signature, so both
// credential fields stay connection-scoped instead of being injected as
// headers.
package dingtalkbot

import (
	"context"
	"fmt"

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

const (
	// Domain is the Extension domain.
	Domain = "dingtalkbot"
	// Revision is the implementation identity folded into the runtime digest.
	Revision = "dingtalkbot-rest-v1"
	// BaseURL is the fixed DingTalk open API origin.
	BaseURL = "https://oapi.dingtalk.com"
	// APIKeyField is the connection credential carrying the webhook access
	// token (or the full webhook URL).
	APIKeyField = "apiKey"
	// SigningSecretField is the optional connection credential enabling
	// DingTalk signature security mode.
	SigningSecretField = "signingSecret"
)

// Extension composes the DingTalk Bot Builtin. It fails closed when the
// credential source or provider client cannot be constructed.
func Extension(credentials runner.CredentialSource) (extension.Extension, error) {
	client, err := runner.NewRESTClient(runner.RESTOptions{
		BaseURL: BaseURL,
		Auth:    AuthSpec(),
	})
	if err != nil {
		return extension.Extension{}, fmt.Errorf("dingtalkbot: build REST client: %w", err)
	}
	executor := runner.NewRESTExecutor(Domain, client, credentials, Handlers())
	return extension.Extension{
		Definition: Definition(),
		Implementation: runner.Implementation{
			Executor:            executor,
			Revision:            Revision,
			ConnectionPolicy:    registry.ConnectionRequired,
			Auth:                AuthSpec(),
			ValidateCredentials: ValidateCredentials,
		},
		Distribution: extension.Distribution{
			AppID:                     "app_dingtalk_bot",
			AppSlug:                   "dingtalkbot",
			DefaultName:               Domain,
			DefaultEnabled:            true,
			IconURL:                   "/app-icons/dingtalkbot.svg",
			GenericProviderConnection: true,
		},
	}, nil
}

// AuthSpec declares the webhook credential contract. Both fields are
// connection-scoped (no header binding): the access token and signature
// become query parameters, so the runtime injects nothing on its own.
func AuthSpec() runner.AuthSpec {
	return runner.AuthSpec{
		Kind: runner.AuthCredential,
		Fields: []runner.CredentialField{
			{
				Name:        APIKeyField,
				DisplayName: "DingTalk webhook access token",
				Description: "The access_token query value of the DingTalk custom bot webhook URL; the full webhook URL is also accepted.",
				Required:    true,
				Secret:      true,
			},
			{
				Name:        SigningSecretField,
				DisplayName: "DingTalk signing secret",
				Description: "Optional SEC-prefixed secret used when the DingTalk bot enables signature security mode.",
				Required:    false,
				Secret:      true,
			},
		},
	}
}

// ValidateCredentials live-checks the webhook with one bounded probe whose
// msgtype DingTalk rejects, treating the structured rejection as proof the
// webhook and signature are accepted.
func ValidateCredentials(ctx context.Context, credentials runner.Credentials) error {
	client, err := runner.NewRESTClient(runner.RESTOptions{
		BaseURL: BaseURL,
		Auth:    AuthSpec(),
	})
	if err != nil {
		return err
	}
	_, _, probeErr := callWebhook(ctx, client, credentials, map[string]any{
		"msgtype": "__validation_probe__",
	})
	return probeErr
}
