package registry

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

var (
	developerSlugPattern   = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,62}$`)
	developerSemverPattern = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$`)
)

var manifestAllowedScopes = map[string]struct{}{
	"app.mentions.read":             {},
	"app.notifications.write":       {},
	"app.notification_actions.read": {},
	"files.write":                   {},
	"messages.write":                {},
	"messages.read":                 {},
	"threads.read":                  {},
	"subscriptions.manage":          {},
	"members.read":                  {},
	"profile.read":                  {},
}

// ValidatedDeveloperManifest is a normalized developer-supplied app manifest.
type ValidatedDeveloperManifest struct {
	Version     string
	CallbackURL string
	Payload     []byte
}

type developerRuntimeCapabilityManifest struct {
	Runtime       developerAppRuntimeManifest        `json:"runtime"`
	Events        []string                           `json:"events"`
	Scopes        []string                           `json:"scopes"`
	Notifications *developerAppNotificationsManifest `json:"notifications,omitempty"`
	ConfigSchema  any                                `json:"config_schema"`
}

type developerAppNotificationsManifest struct {
	Targets []string `json:"targets"`
	Content []string `json:"content"`
}

type developerAppRuntimeManifest struct {
	Transports       []string `json:"transports"`
	DefaultTransport string   `json:"default_transport"`
}

// NormalizeDeveloperSlug validates a developer app slug.
func NormalizeDeveloperSlug(value string) (string, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "", badRequest("slug is required")
	}
	if !developerSlugPattern.MatchString(trimmed) {
		return "", badRequest("slug must use lowercase letters, numbers, or hyphens")
	}
	return trimmed, nil
}

// NormalizeDeveloperVisibility validates a developer app visibility value.
func NormalizeDeveloperVisibility(value string) (string, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "private", nil
	}
	switch trimmed {
	case "public", "unlisted", "private":
		return trimmed, nil
	default:
		return "", badRequest("visibility must be public, unlisted, or private")
	}
}

// ValidateDeveloperManifest validates and normalizes a developer app manifest payload.
func ValidateDeveloperManifest(appSlug string, appKind string, raw []byte) (ValidatedDeveloperManifest, error) {
	if len(raw) == 0 {
		return ValidatedDeveloperManifest{}, badRequest("manifest is required")
	}

	var document map[string]any
	if err := model.UnmarshalJSONObject("developer app manifest", raw, &document); err != nil {
		return ValidatedDeveloperManifest{}, badRequest("manifest must be a JSON object")
	}
	if document == nil {
		return ValidatedDeveloperManifest{}, badRequest("manifest must be a JSON object")
	}

	schemaVersion, err := requireStringField(document, "schema_version")
	if err != nil {
		return ValidatedDeveloperManifest{}, err
	}
	if schemaVersion != "1" {
		return ValidatedDeveloperManifest{}, badRequest("manifest schema_version must be \"1\"")
	}

	manifestSlug, err := requireStringField(document, "slug")
	if err != nil {
		return ValidatedDeveloperManifest{}, err
	}
	if manifestSlug != strings.TrimSpace(appSlug) {
		return ValidatedDeveloperManifest{}, badRequest("manifest slug must match the app slug")
	}

	version, err := requireStringField(document, "version")
	if err != nil {
		return ValidatedDeveloperManifest{}, err
	}
	if !developerSemverPattern.MatchString(version) {
		return ValidatedDeveloperManifest{}, badRequest("manifest version must be valid semver")
	}

	runtimeCapability, hasRuntimeCapability, err := normalizeRuntimeCapability(document)
	if err != nil {
		return ValidatedDeveloperManifest{}, err
	}
	scopes, err := optionalManifestStringSliceField(document, "scopes")
	if err != nil {
		return ValidatedDeveloperManifest{}, err
	}
	for _, scope := range SortedUniqueScopes(scopes) {
		if !isRuntimeCapabilityScope(scope) {
			continue
		}
		if !hasRuntimeCapability || !containsString(runtimeCapability.Scopes, scope) {
			return ValidatedDeveloperManifest{}, badRequest(fmt.Sprintf("manifest scope %q must be declared by capabilities.runtime.scopes", scope))
		}
	}
	if hasRuntimeCapability {
		scopes = SortedUniqueScopes(append(scopes, runtimeCapability.Scopes...))
		document["scopes"] = scopes
		document["capabilities"].(map[string]any)["runtime"] = runtimeCapabilityDocument(runtimeCapability)
	}
	for _, scope := range scopes {
		if _, ok := manifestAllowedScopes[scope]; !ok {
			return ValidatedDeveloperManifest{}, badRequest(fmt.Sprintf("manifest scope %q is not supported", scope))
		}
	}

	if value, ok := document["config_schema"]; ok {
		payload, err := model.MarshalJSONObject("manifest config_schema", value)
		if err != nil {
			return ValidatedDeveloperManifest{}, badRequest("manifest config_schema must be valid JSON")
		}
		if _, err := CompileManifestSchema("config", string(payload)); err != nil {
			return ValidatedDeveloperManifest{}, badRequest("App manifest config schema is invalid.")
		}
	}
	if value, ok := document["credentials_schema"]; ok {
		payload, err := model.MarshalJSONObject("manifest credentials_schema", value)
		if err != nil {
			return ValidatedDeveloperManifest{}, badRequest("manifest credentials_schema must be valid JSON")
		}
		if _, err := CompileManifestSchema("credentials", string(payload)); err != nil {
			return ValidatedDeveloperManifest{}, badRequest("App manifest credentials schema is invalid.")
		}
	}

	callbackURL, err := manifestCallbackURL(document)
	if err != nil {
		return ValidatedDeveloperManifest{}, err
	}
	if strings.TrimSpace(appKind) == "third_party" {
		if callbackURL == "" && !hasRuntimeCapability {
			return ValidatedDeveloperManifest{}, badRequest("manifest callback.url is required for third-party apps")
		}
		if callbackURL != "" {
			parsedURL, err := url.Parse(callbackURL)
			if err != nil || parsedURL.Scheme != "https" || strings.TrimSpace(parsedURL.Host) == "" {
				return ValidatedDeveloperManifest{}, badRequest("manifest callback.url must be an HTTPS URL")
			}
		}
	}

	normalizedPayload, err := model.MarshalJSONObject("developer app manifest", document)
	if err != nil {
		return ValidatedDeveloperManifest{}, badRequest("manifest must be valid JSON")
	}
	return ValidatedDeveloperManifest{
		Version:     version,
		CallbackURL: callbackURL,
		Payload:     normalizedPayload,
	}, nil
}

func requireStringField(document map[string]any, field string) (string, error) {
	value, ok := document[field]
	if !ok {
		return "", badRequest(fmt.Sprintf("manifest %s is required", field))
	}
	parsed, ok := value.(string)
	if !ok || strings.TrimSpace(parsed) == "" {
		return "", badRequest(fmt.Sprintf("manifest %s must be a non-empty string", field))
	}
	return strings.TrimSpace(parsed), nil
}

func optionalManifestStringSliceField(document map[string]any, field string) ([]string, error) {
	value, ok := document[field]
	if !ok || value == nil {
		return []string{}, nil
	}

	rawItems, ok := value.([]any)
	if !ok {
		return nil, badRequest(fmt.Sprintf("manifest %s must be an array", field))
	}

	items := make([]string, 0, len(rawItems))
	for _, rawItem := range rawItems {
		parsed, ok := rawItem.(string)
		if !ok || strings.TrimSpace(parsed) == "" {
			return nil, badRequest(fmt.Sprintf("manifest %s entries must be non-empty strings", field))
		}
		items = append(items, strings.TrimSpace(parsed))
	}
	return items, nil
}

func normalizeRuntimeCapability(document map[string]any) (developerRuntimeCapabilityManifest, bool, error) {
	rawCapabilities, ok := document["capabilities"]
	if !ok || rawCapabilities == nil {
		return developerRuntimeCapabilityManifest{}, false, nil
	}
	capabilities, ok := rawCapabilities.(map[string]any)
	if !ok {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities must be an object")
	}
	rawApp, ok := capabilities["runtime"]
	if !ok || rawApp == nil {
		return developerRuntimeCapabilityManifest{}, false, nil
	}
	app, ok := rawApp.(map[string]any)
	if !ok {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime must be an object")
	}
	rawRuntime, ok := app["runtime"].(map[string]any)
	if !ok {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.runtime must be an object")
	}
	transports, err := optionalManifestStringSliceField(rawRuntime, "transports")
	if err != nil {
		return developerRuntimeCapabilityManifest{}, false, err
	}
	transports = UniqueTrimmedStrings(transports)
	if len(transports) == 0 {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.runtime.transports is required")
	}
	for _, transport := range transports {
		switch transport {
		case "callback", "runner_stream":
		default:
			return developerRuntimeCapabilityManifest{}, false, badRequest(fmt.Sprintf("manifest capabilities.runtime.runtime transport %q is not supported", transport))
		}
	}
	var defaultTransport string
	if rawDefaultTransport, ok := rawRuntime["default_transport"]; ok && rawDefaultTransport != nil {
		parsedDefaultTransport, ok := rawDefaultTransport.(string)
		if !ok {
			return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.runtime.default_transport must be a string")
		}
		defaultTransport = strings.TrimSpace(parsedDefaultTransport)
	}
	if defaultTransport == "" {
		defaultTransport = transports[0]
	}
	if !containsString(transports, defaultTransport) {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.runtime.default_transport must be listed in transports")
	}
	events, err := optionalManifestStringSliceField(app, "events")
	if err != nil {
		return developerRuntimeCapabilityManifest{}, false, err
	}
	events, err = NormalizeAppEvents(events)
	if err != nil {
		return developerRuntimeCapabilityManifest{}, false, badRequest(fmt.Sprintf("manifest capabilities.runtime.events %s", err))
	}
	appScopes, err := optionalManifestStringSliceField(app, "scopes")
	if err != nil {
		return developerRuntimeCapabilityManifest{}, false, err
	}
	appScopes = SortedUniqueScopes(appScopes)
	notifications, err := normalizeAppNotifications(app)
	if err != nil {
		return developerRuntimeCapabilityManifest{}, false, err
	}
	hasMentionEvent := containsString(events, AppEventMessageMention)
	hasActionEvent := containsString(events, AppEventNotificationAction)
	if !hasMentionEvent && notifications == nil {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime must declare a supported event or notifications capability")
	}
	if hasMentionEvent && (!containsString(appScopes, "app.mentions.read") || !containsString(appScopes, "messages.write")) {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.scopes must include app.mentions.read and messages.write")
	}
	if !hasMentionEvent && containsString(appScopes, "app.mentions.read") {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.scopes app.mentions.read requires agentteam.message.mention")
	}
	if notifications != nil {
		if !containsString(appScopes, "app.notifications.write") || !containsString(appScopes, "messages.write") {
			return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.notifications requires app.notifications.write and messages.write")
		}
	} else if containsString(appScopes, "app.notifications.write") {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.scopes app.notifications.write requires capabilities.runtime.notifications")
	}
	if hasActionEvent {
		if notifications == nil || !containsString(notifications.Content, AppNotificationContentInteractiveCard) {
			return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.events agentteam.app.notification.action requires interactive_card notification content")
		}
		if !containsString(appScopes, "app.notification_actions.read") {
			return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.events agentteam.app.notification.action requires app.notification_actions.read")
		}
	} else if containsString(appScopes, "app.notification_actions.read") {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.scopes app.notification_actions.read requires agentteam.app.notification.action")
	}
	configSchema := app["config_schema"]
	if configSchema == nil {
		configSchema = map[string]any{"type": "object"}
	}
	configPayload, err := model.MarshalJSONObject("manifest capabilities.runtime.config_schema", configSchema)
	if err != nil {
		return developerRuntimeCapabilityManifest{}, false, badRequest("manifest capabilities.runtime.config_schema must be valid JSON")
	}
	if _, err := CompileManifestSchema("app config", string(configPayload)); err != nil {
		return developerRuntimeCapabilityManifest{}, false, badRequest("App manifest app config schema is invalid.")
	}
	return developerRuntimeCapabilityManifest{
		Runtime: developerAppRuntimeManifest{
			Transports:       transports,
			DefaultTransport: defaultTransport,
		},
		Events:        events,
		Scopes:        appScopes,
		Notifications: notifications,
		ConfigSchema:  configSchema,
	}, true, nil
}

func runtimeCapabilityDocument(app developerRuntimeCapabilityManifest) map[string]any {
	document := map[string]any{
		"runtime": map[string]any{
			"transports":        app.Runtime.Transports,
			"default_transport": app.Runtime.DefaultTransport,
		},
		"events":        app.Events,
		"scopes":        app.Scopes,
		"config_schema": app.ConfigSchema,
	}
	if app.Notifications != nil {
		document["notifications"] = map[string]any{
			"targets": app.Notifications.Targets,
			"content": app.Notifications.Content,
		}
	}
	return document
}

func normalizeAppNotifications(app map[string]any) (*developerAppNotificationsManifest, error) {
	rawNotifications, ok := app["notifications"]
	if !ok || rawNotifications == nil {
		return nil, nil
	}
	notifications, ok := rawNotifications.(map[string]any)
	if !ok {
		return nil, badRequest("manifest capabilities.runtime.notifications must be an object")
	}
	targets, err := optionalManifestStringSliceField(notifications, "targets")
	if err != nil {
		return nil, err
	}
	targets, err = normalizeAppNotificationValues("target", targets, manifestAppNotificationTargets)
	if err != nil {
		return nil, badRequest(fmt.Sprintf("manifest capabilities.runtime.notifications %s", err))
	}
	if len(targets) == 0 {
		return nil, badRequest("manifest capabilities.runtime.notifications.targets is required")
	}
	content, err := optionalManifestStringSliceField(notifications, "content")
	if err != nil {
		return nil, err
	}
	content, err = normalizeAppNotificationValues("content kind", content, manifestAppNotificationContentKinds)
	if err != nil {
		return nil, badRequest(fmt.Sprintf("manifest capabilities.runtime.notifications %s", err))
	}
	if len(content) == 0 {
		return nil, badRequest("manifest capabilities.runtime.notifications.content is required")
	}
	return &developerAppNotificationsManifest{Targets: targets, Content: content}, nil
}

func containsString(values []string, needle string) bool {
	for _, value := range values {
		if strings.TrimSpace(value) == needle {
			return true
		}
	}
	return false
}

func manifestCallbackURL(document map[string]any) (string, error) {
	value, ok := document["callback"]
	if !ok || value == nil {
		return "", nil
	}
	callbackObject, ok := value.(map[string]any)
	if !ok {
		return "", badRequest("manifest callback must be an object")
	}
	rawURL, ok := callbackObject["url"]
	if !ok || rawURL == nil {
		return "", nil
	}
	parsedURL, ok := rawURL.(string)
	if !ok {
		return "", badRequest("manifest callback.url must be a string")
	}
	return strings.TrimSpace(parsedURL), nil
}
