// Package providers aggregates every trusted Builtin provider Extension that
// ships with the AgentTeam binary into one registration surface.
//
// Migrated REST providers are ported from OpenConnector
// (https://github.com/oomol-lab/open-connector) and keep the baseline commit
// recorded below so later batches can diff and migrate incrementally.
package providers

import (
	"fmt"

	"github.com/tidbcloud/AgentTeam/internal/extension"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/aifin"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/alphavantage"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/arxiv"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/brave"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/dingtalkbot"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/feishubot"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/financialmodelingprep"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/gitea"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/github"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/hackernews"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/hotnews"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/jenkins"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/jin10"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/mailruntime"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/notion"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/ones"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/pkulaw"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/qichacha"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/searchengine"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/searxng"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/serper"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/tavily"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/tianyancha"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/todoist"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/tushare"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/wecombot"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
	"github.com/tidbcloud/AgentTeam/internal/service/prwatch"
)

// OpenConnectorBaseline is the upstream open-connector commit the migrated
// provider catalogs were ported from. Bump it (and re-audit the per-provider
// diffs) when a later batch migrates more providers.
const OpenConnectorBaseline = "267a9830e34d6128aea8424b92d6ff355bc1d8cf"

// Deps carries the server-owned services provider executors resolve through.
type Deps struct {
	// WatchService backs the GitHub/Gitea pull-request-watch Builtins.
	WatchService prwatch.Invoker

	// SearchService backs the search engine Builtins.
	SearchService searchengine.Invoker

	// Credentials resolves App connection credentials for token-authenticated
	// REST providers.
	Credentials runner.CredentialSource

	// Connections resolves credentials plus provider-owned prepared config for
	// providers whose startup-validated topology is persisted per
	// installation.
	Connections runner.ConnectionSource

	// MailService backs the Email Builtin. Mail owns its IMAP/SMTP runtime
	// and multi-field connection API instead of the generic REST credential
	// contract.
	MailService mailruntime.Invoker
}

// Extensions composes the full trusted Builtin provider set in stable domain
// order. It fails closed when a required dependency is missing so a
// mis-wired profile cannot start with silently dead providers.
func Extensions(deps Deps) ([]extension.Extension, error) {
	if deps.WatchService == nil {
		return nil, fmt.Errorf("providers: pull-request watch service is required")
	}
	if deps.SearchService == nil {
		return nil, fmt.Errorf("providers: search service is required")
	}
	if deps.Credentials == nil {
		return nil, fmt.Errorf("providers: credential source is required")
	}
	if deps.Connections == nil {
		return nil, fmt.Errorf("providers: connection source is required")
	}
	jenkinsExtension, err := jenkins.Extension(deps.Credentials)
	if err != nil {
		return nil, fmt.Errorf("providers: Jenkins extension: %w", err)
	}
	if deps.MailService == nil {
		return nil, fmt.Errorf("providers: mail service is required")
	}
	list := []extension.Extension{
		mail.Extension(deps.MailService),
		github.Extension(deps.WatchService),
		gitea.Extension(deps.WatchService),
		searxng.Extension(deps.SearchService),
		brave.Extension(deps.SearchService),
		tavily.Extension(deps.SearchService),
		serper.Extension(deps.SearchService),
		arxiv.Extension(),
		jenkinsExtension,
	}
	migrated, err := migratedExtensions(deps)
	if err != nil {
		return nil, err
	}
	list = append(list, migrated...)
	for _, ext := range list {
		if err := validateGenericProviderConnection(ext); err != nil {
			return nil, err
		}
	}
	return list, nil
}

func validateGenericProviderConnection(ext extension.Extension) error {
	if !ext.Distribution.GenericProviderConnection {
		return nil
	}
	if ext.Implementation.Auth.Kind == runner.AuthNone ||
		len(ext.Implementation.Auth.Fields) == 0 {
		return fmt.Errorf(
			"providers: %s generic provider connection requires a non-empty auth contract",
			ext.Definition.Domain,
		)
	}
	if err := ext.Implementation.Auth.ValidateSpec(); err != nil {
		return fmt.Errorf(
			"providers: %s generic provider connection has an invalid auth contract: %w",
			ext.Definition.Domain,
			err,
		)
	}
	hasValidator := ext.Implementation.ValidateCredentials != nil
	hasPreparer := ext.Implementation.PrepareConnection != nil
	if hasValidator == hasPreparer {
		return fmt.Errorf(
			"providers: %s generic provider connection requires exactly one credential validator or preparer",
			ext.Definition.Domain,
		)
	}
	if err := ext.Implementation.ConnectionConfig.ValidateSpec(); err != nil {
		return fmt.Errorf(
			"providers: %s generic provider connection has an invalid config contract: %w",
			ext.Definition.Domain,
			err,
		)
	}
	if len(ext.Implementation.ConnectionConfig.Fields) != 0 &&
		ext.Implementation.PrepareConnection == nil {
		return fmt.Errorf(
			"providers: %s generic provider connection declares config without a preparer",
			ext.Definition.Domain,
		)
	}
	return nil
}

// migratedExtensions composes the OpenConnector-migrated REST providers.
func migratedExtensions(deps Deps) ([]extension.Extension, error) {
	todoistExtension, err := todoist.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	notionExtension, err := notion.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	hackernewsExtension, err := hackernews.Extension()
	if err != nil {
		return nil, err
	}
	hotnewsExtension, err := hotnews.Extension()
	if err != nil {
		return nil, err
	}
	onesExtension, err := ones.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	feishuCustomBotExtension, err := feishubot.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	dingtalkBotExtension, err := dingtalkbot.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	wecomBotExtension, err := wecombot.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	tianyanchaExtension, err := tianyancha.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	qichachaExtension, err := qichacha.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	tushareExtension, err := tushare.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	alphaVantageExtension, err := alphavantage.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	financialModelingPrepExtension, err := financialmodelingprep.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	jin10Extension, err := jin10.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	aifinExtension, err := aifin.Extension(deps.Credentials)
	if err != nil {
		return nil, err
	}
	pkulawExtension, err := pkulaw.Extension(deps.Connections)
	if err != nil {
		return nil, err
	}
	return []extension.Extension{
		todoistExtension,
		hackernewsExtension,
		notionExtension,
		onesExtension,
		hotnewsExtension,
		feishuCustomBotExtension,
		dingtalkBotExtension,
		wecomBotExtension,
		tianyanchaExtension,
		qichachaExtension,
		tushareExtension,
		alphaVantageExtension,
		financialModelingPrepExtension,
		jin10Extension,
		aifinExtension,
		pkulawExtension,
	}, nil
}

// ExtensionsByAppID indexes the aggregate by product App identity for the
// connection flow (installation → App → provider).
func ExtensionsByAppID(deps Deps) (map[string]extension.Extension, error) {
	list, err := Extensions(deps)
	if err != nil {
		return nil, err
	}
	byAppID := make(map[string]extension.Extension, len(list))
	for _, extension := range list {
		if _, duplicate := byAppID[extension.Distribution.AppID]; duplicate {
			return nil, fmt.Errorf(
				"providers: duplicate App ID %q",
				extension.Distribution.AppID,
			)
		}
		byAppID[extension.Distribution.AppID] = extension
	}
	return byAppID, nil
}
