package installation

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// AgentAppMCPTools is the canonical provider MCP tool list for an App slug.
// It is the single source shared by the definition capability sync and the
// agent authorization surface; a divergence here silently reshapes every
// installation of the App.
func AgentAppMCPTools(appSlug string) []string {
	if !providerWatchAppSlug(appSlug) {
		return []string{}
	}
	return []string{"watch_pull_request", "list_watches", "pause_watch", "resume_watch", "unwatch"}
}

// DefaultAgentAppAuthorizationScopes is the canonical default scope set for
// provider-watch Apps.
func DefaultAgentAppAuthorizationScopes(appSlug string) []string {
	if !providerWatchAppSlug(appSlug) {
		return []string{}
	}
	return []string{"service_event_subscription.read", "service_event_subscription.write"}
}

func providerWatchAppSlug(appSlug string) bool {
	switch strings.ToLower(strings.TrimSpace(appSlug)) {
	case registry.GitHubAppSlug, registry.GiteaAppSlug:
		return true
	default:
		return false
	}
}

func normalizeScopeSet(scopes []string) []string {
	out := make([]string, 0, len(scopes))
	seen := make(map[string]struct{}, len(scopes))
	for _, scope := range scopes {
		trimmed := strings.TrimSpace(scope)
		if trimmed == "" {
			continue
		}
		if _, exists := seen[trimmed]; exists {
			continue
		}
		seen[trimmed] = struct{}{}
		out = append(out, trimmed)
	}
	return out
}

func agentAppCapabilityConfigJSON(appSlug string, commands any) []byte {
	config := map[string]any{
		"commands": commands,
		"tools":    AgentAppMCPTools(appSlug),
		"scopes":   DefaultAgentAppAuthorizationScopes(appSlug),
	}
	payload, err := model.MarshalJSONObject("service app MCP capability config", config)
	if err != nil {
		return []byte(`{}`)
	}
	return payload
}

type storedAppServiceCapabilityManifest struct {
	Commands   any      `json:"commands"`
	EventTypes []any    `json:"event_types"`
	Scopes     []string `json:"scopes"`
}

// SyncDefinitionCapabilities replays the complete manifest-derived capability
// set (MCP tools, events, subscriptions, runtime capability, scope
// capabilities, built-in Extensions) for one definition.
func SyncDefinitionCapabilities(ctx context.Context, txStore *serviceactor.TxStore, definition serviceactor.Definition, manifestJSON []byte, now time.Time) error {
	manifest, runtimeCapability, err := decodeStoredAppServiceCapabilityManifest(manifestJSON)
	if err != nil {
		return err
	}
	commands := manifest.Commands
	if commands == nil {
		commands = []any{}
	}
	mcpConfig := agentAppCapabilityConfigJSON(definition.Slug, commands)
	if len(AgentAppMCPTools(definition.Slug)) > 0 || len(asSlice(commands)) > 0 {
		if _, err := txStore.UpsertCapability(ctx, serviceactor.CapabilityUpsert{
			ServiceDefinitionID: definition.ID,
			ServiceKind:         serviceactor.KindApp,
			CapabilityKey:       serviceactor.CapabilityMCPTools,
			Config:              mcpConfig,
			DefaultEnabled:      true,
			Now:                 now,
		}); err != nil {
			return fmt.Errorf("sync app MCP service capability: %w", err)
		}
	}
	if len(manifest.EventTypes) > 0 {
		eventConfig, err := model.MarshalJSONObject("service app event capability config", map[string]any{
			"eventTypes":  manifest.EventTypes,
			"webhookPath": "/api/apps/" + definition.Slug + "/webhook",
		})
		if err != nil {
			return err
		}
		if _, err := txStore.UpsertCapability(ctx, serviceactor.CapabilityUpsert{
			ServiceDefinitionID: definition.ID,
			ServiceKind:         serviceactor.KindApp,
			CapabilityKey:       serviceactor.CapabilityEventsReceive,
			Config:              eventConfig,
			DefaultEnabled:      true,
			Now:                 now,
		}); err != nil {
			return fmt.Errorf("sync app event service capability: %w", err)
		}
		subscriptionConfig, err := model.MarshalJSONObject("service app subscription capability config", map[string]any{"subscriptionTypes": []string{"pull_request"}})
		if err != nil {
			return err
		}
		if _, err := txStore.UpsertCapability(ctx, serviceactor.CapabilityUpsert{
			ServiceDefinitionID: definition.ID,
			ServiceKind:         serviceactor.KindApp,
			CapabilityKey:       serviceactor.CapabilitySubscriptionsManage,
			Config:              subscriptionConfig,
			DefaultEnabled:      true,
			Now:                 now,
		}); err != nil {
			return fmt.Errorf("sync app subscription service capability: %w", err)
		}
	}
	if runtimeCapability.Supported {
		appConfig, err := appServiceCapabilityConfigJSON(runtimeCapability)
		if err != nil {
			return err
		}
		if _, err := txStore.UpsertCapability(ctx, serviceactor.CapabilityUpsert{
			ServiceDefinitionID: definition.ID,
			ServiceKind:         serviceactor.KindApp,
			CapabilityKey:       serviceactor.CapabilityApp,
			Config:              appConfig,
			DefaultEnabled:      true,
			Now:                 now,
		}); err != nil {
			return fmt.Errorf("sync app app service capability: %w", err)
		}
	}
	if extensions, ok := BuiltinExtensionCapabilityConfig(definition.ID); ok {
		config, err := model.EncodeExtensionCapabilityConfig(extensions)
		if err != nil {
			return fmt.Errorf("encode built-in App Extension capability: %w", err)
		}
		if _, err := txStore.UpsertCapability(ctx, serviceactor.CapabilityUpsert{
			ServiceDefinitionID: definition.ID,
			ServiceKind:         serviceactor.KindApp,
			CapabilityKey:       serviceactor.CapabilityExtensions,
			Config:              config,
			DefaultEnabled:      true,
			Now:                 now,
		}); err != nil {
			return fmt.Errorf("sync built-in App Extension capability: %w", err)
		}
	}
	for _, scope := range manifest.Scopes {
		capabilityKey := appServiceCapabilityKeyForScope(scope)
		if capabilityKey == "" {
			continue
		}
		config, err := model.MarshalJSONObject("service app grant capability config", map[string]any{"source": "app_scope"})
		if err != nil {
			return err
		}
		if _, err := txStore.UpsertCapability(ctx, serviceactor.CapabilityUpsert{
			ServiceDefinitionID: definition.ID,
			ServiceKind:         serviceactor.KindApp,
			CapabilityKey:       capabilityKey,
			Config:              config,
			DefaultEnabled:      true,
			Now:                 now,
		}); err != nil {
			return fmt.Errorf("sync app grant service capability: %w", err)
		}
	}
	return nil
}

func decodeStoredAppServiceCapabilityManifest(payload []byte) (storedAppServiceCapabilityManifest, registry.StoredAppRuntimeCapability, error) {
	manifest := storedAppServiceCapabilityManifest{Commands: []any{}, EventTypes: []any{}, Scopes: []string{}}
	runtimeCapability, err := registry.ParseStoredAppRuntimeCapability(payload)
	if err != nil {
		return storedAppServiceCapabilityManifest{}, registry.StoredAppRuntimeCapability{}, err
	}
	if len(payload) == 0 {
		return manifest, runtimeCapability, nil
	}
	var document map[string]any
	if err := model.UnmarshalJSONObject("stored app service capability manifest", payload, &document); err != nil || document == nil {
		if err != nil {
			return storedAppServiceCapabilityManifest{}, registry.StoredAppRuntimeCapability{}, err
		}
		return manifest, runtimeCapability, nil
	}
	if commands, ok := document["commands"]; ok && commands != nil {
		manifest.Commands = commands
	}
	if values, ok := document["event_types"].([]any); ok {
		manifest.EventTypes = values
	}
	if values, ok := document["scopes"].([]any); ok {
		for _, value := range values {
			if scope, ok := value.(string); ok {
				manifest.Scopes = append(manifest.Scopes, scope)
			}
		}
	}
	if runtimeCapability.Supported {
		manifest.Scopes = append(manifest.Scopes, runtimeCapability.Scopes...)
	}
	manifest.Scopes = normalizeScopeSet(manifest.Scopes)
	return manifest, runtimeCapability, nil
}

func appServiceCapabilityConfigJSON(app registry.StoredAppRuntimeCapability) ([]byte, error) {
	config := map[string]any{
		"events": app.Events,
		"scopes": app.Scopes,
	}
	if app.Notifications.Supported {
		config["notifications"] = map[string]any{
			"supported": app.Notifications.Supported,
			"targets":   app.Notifications.Targets,
			"content":   app.Notifications.Content,
		}
	}
	return model.MarshalJSONObject("service app app capability config", config)
}

func asSlice(value any) []any {
	if values, ok := value.([]any); ok {
		return values
	}
	return nil
}

func appServiceCapabilityKeyForScope(scope string) string {
	switch strings.TrimSpace(scope) {
	case "messages.write":
		return serviceactor.CapabilityMessageWrite
	case "app.notifications.write":
		return serviceactor.CapabilityAppNotificationsWrite
	case "app.notification_actions.read":
		return serviceactor.CapabilityAppNotificationActionsRead
	default:
		return ""
	}
}

func BuiltinExtensionCapabilityConfig(
	serviceDefinitionID string,
) (model.ExtensionCapabilityConfig, bool) {
	trimmed := strings.TrimSpace(serviceDefinitionID)
	for _, resolver := range []func(string) (model.ExtensionCapabilityEntry, bool){
		imBotExtensionCapabilityEntry,
		searchEngineExtensionCapabilityEntry,
		dataProviderExtensionCapabilityEntry,
	} {
		if entry, ok := resolver(trimmed); ok {
			return model.ExtensionCapabilityConfig{
				Extensions: []model.ExtensionCapabilityEntry{entry},
			}, true
		}
	}
	var entry model.ExtensionCapabilityEntry
	switch trimmed {
	case "app_github":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "github",
			Version:          1,
			DefinitionDigest: model.GitHubBuiltinExtensionDefinitionDigest,
			DefaultName:      "github",
			DefaultEnabled:   true,
		}
	case "app_gitea":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "gitea",
			Version:          1,
			DefinitionDigest: model.GiteaBuiltinExtensionDefinitionDigest,
			DefaultName:      "gitea",
			DefaultEnabled:   true,
		}
	case "app_tianyancha":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "tianyancha",
			Version:          1,
			DefinitionDigest: model.TianyanchaBuiltinExtensionDefinitionDigest,
			DefaultName:      "tianyancha",
			DefaultEnabled:   true,
		}
	case "app_qichacha":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "qichacha",
			Version:          1,
			DefinitionDigest: model.QichachaBuiltinExtensionDefinitionDigest,
			DefaultName:      "qichacha",
			DefaultEnabled:   true,
		}
	case "app_arxiv":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "arxiv",
			Version:          1,
			DefinitionDigest: model.ArxivBuiltinExtensionDefinitionDigest,
			DefaultName:      "arxiv",
			DefaultEnabled:   true,
		}
	case "app_todoist":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "todoist",
			Version:          1,
			DefinitionDigest: model.TodoistBuiltinExtensionDefinitionDigest,
			DefaultName:      "todoist",
			DefaultEnabled:   true,
		}
	case "app_notion":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "notion",
			Version:          1,
			DefinitionDigest: model.NotionBuiltinExtensionDefinitionDigest,
			DefaultName:      "notion",
			DefaultEnabled:   true,
		}
	case "app_hackernews":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "hackernews",
			Version:          1,
			DefinitionDigest: model.HackernewsBuiltinExtensionDefinitionDigest,
			DefaultName:      "hackernews",
			DefaultEnabled:   true,
		}
	case "app_jenkins":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "jenkins",
			Version:          1,
			DefinitionDigest: model.JenkinsBuiltinExtensionDefinitionDigest,
			DefaultName:      "jenkins",
			DefaultEnabled:   true,
		}
	case "app_ones":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "ones",
			Version:          1,
			DefinitionDigest: model.OnesBuiltinExtensionDefinitionDigest,
			DefaultName:      "ones",
			DefaultEnabled:   true,
		}
	case "app_hotnews":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "hotnews",
			Version:          1,
			DefinitionDigest: model.HotNewsBuiltinExtensionDefinitionDigest,
			DefaultName:      "hotnews",
			DefaultEnabled:   true,
		}
	default:
		return model.ExtensionCapabilityConfig{}, false
	}
	return model.ExtensionCapabilityConfig{
		Extensions: []model.ExtensionCapabilityEntry{entry},
	}, true
}

// searchEngineExtensionCapabilityEntry maps the search-engine product Apps
// onto their digest-pinned capability entries.
func searchEngineExtensionCapabilityEntry(serviceDefinitionID string) (model.ExtensionCapabilityEntry, bool) {
	var entry model.ExtensionCapabilityEntry
	switch serviceDefinitionID {
	case "app_searxng":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "searxng",
			Version:          1,
			DefinitionDigest: model.SearXNGBuiltinExtensionDefinitionDigest,
			DefaultName:      "searxng",
			DefaultEnabled:   true,
		}
	case "app_brave_search":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "brave",
			Version:          1,
			DefinitionDigest: model.BraveBuiltinExtensionDefinitionDigest,
			DefaultName:      "brave",
			DefaultEnabled:   true,
		}
	case "app_tavily":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "tavily",
			Version:          1,
			DefinitionDigest: model.TavilyBuiltinExtensionDefinitionDigest,
			DefaultName:      "tavily",
			DefaultEnabled:   true,
		}
	case "app_serper":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "serper",
			Version:          1,
			DefinitionDigest: model.SerperBuiltinExtensionDefinitionDigest,
			DefaultName:      "serper",
			DefaultEnabled:   true,
		}
	default:
		return model.ExtensionCapabilityEntry{}, false
	}
	return entry, true
}

// imBotExtensionCapabilityEntry maps the three IM webhook-bot product Apps
// onto their digest-pinned capability entries.
func imBotExtensionCapabilityEntry(serviceDefinitionID string) (model.ExtensionCapabilityEntry, bool) {
	var entry model.ExtensionCapabilityEntry
	switch serviceDefinitionID {
	case "app_feishu_custom_bot":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "feishucustombot",
			Version:          1,
			DefinitionDigest: model.FeishuCustomBotBuiltinExtensionDefinitionDigest,
			DefaultName:      "feishucustombot",
			DefaultEnabled:   true,
		}
	case "app_dingtalk_bot":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "dingtalkbot",
			Version:          1,
			DefinitionDigest: model.DingtalkBotBuiltinExtensionDefinitionDigest,
			DefaultName:      "dingtalkbot",
			DefaultEnabled:   true,
		}
	case "app_wecom_bot":
		entry = model.ExtensionCapabilityEntry{
			Domain:           "wecombot",
			Version:          1,
			DefinitionDigest: model.WecomBotBuiltinExtensionDefinitionDigest,
			DefaultName:      "wecombot",
			DefaultEnabled:   true,
		}
	default:
		return model.ExtensionCapabilityEntry{}, false
	}
	return entry, true
}

// dataProviderExtensionCapabilityEntry maps finance and legal data Apps onto
// their digest-pinned capability entries.
func dataProviderExtensionCapabilityEntry(serviceDefinitionID string) (model.ExtensionCapabilityEntry, bool) {
	var domain, digest string
	switch serviceDefinitionID {
	case "app_tushare":
		domain, digest = "tushare", model.TushareBuiltinExtensionDefinitionDigest
	case "app_alpha_vantage":
		domain, digest = "alpha-vantage", model.AlphaVantageBuiltinExtensionDefinitionDigest
	case "app_financial_modeling_prep":
		domain, digest = "financial-modeling-prep", model.FinancialModelingPrepBuiltinExtensionDefinitionDigest
	case "app_jin10":
		domain, digest = "jin10", model.Jin10BuiltinExtensionDefinitionDigest
	case "app_pkulaw":
		domain, digest = "pkulaw", model.PKULawBuiltinExtensionDefinitionDigest
	case "app_aifinmarket":
		domain, digest = "aifinmarket", model.AIFinMarketBuiltinExtensionDefinitionDigest
	default:
		return model.ExtensionCapabilityEntry{}, false
	}
	return model.ExtensionCapabilityEntry{
		Domain:           domain,
		Version:          1,
		DefinitionDigest: digest,
		DefaultName:      domain,
		DefaultEnabled:   true,
	}, true
}
