package registry

import (
	"reflect"
	"strings"
	"testing"

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

func TestValidateDeveloperManifestNormalizesRuntimeCapability(t *testing.T) {
	manifest, err := ValidateDeveloperManifest("deploy-app", "third_party", []byte(`{
		"schema_version": "1",
		"slug": "deploy-app",
		"version": "1.2.3",
		"scopes": ["profile.read"],
		"capabilities": {
			"runtime": {
				"runtime": {
					"transports": ["runner_stream", "callback"],
					"default_transport": "runner_stream"
				},
				"events": ["agentteam.message.mention"],
				"scopes": ["messages.write", "app.mentions.read"],
				"config_schema": {
					"type": "object",
					"properties": {
						"environment": {"type": "string"}
					}
				}
			}
		}
	}`))
	if err != nil {
		t.Fatalf("ValidateDeveloperManifest returned error: %v", err)
	}
	if manifest.CallbackURL != "" {
		t.Fatalf("CallbackURL = %q, want empty for app manifest without callback endpoint", manifest.CallbackURL)
	}
	var normalized map[string]any
	if err := model.UnmarshalJSONObject("normalized developer manifest", manifest.Payload, &normalized); err != nil {
		t.Fatalf("decode normalized manifest: %v", err)
	}
	scopes, err := OptionalStringSliceField(normalized, "scopes")
	if err != nil {
		t.Fatalf("decode normalized scopes: %v", err)
	}
	if got, want := scopes, []string{"app.mentions.read", "messages.write", "profile.read"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("scopes = %#v, want %#v", got, want)
	}
	app, err := ParseStoredAppRuntimeCapability(manifest.Payload)
	if err != nil {
		t.Fatalf("ParseStoredAppRuntimeCapability returned error: %v", err)
	}
	if !app.Supported {
		t.Fatal("app capability Supported = false, want true")
	}
	if got, want := app.Transports, []string{"runner_stream", "callback"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("app transports = %#v, want %#v", got, want)
	}
	if app.DefaultTransport != "runner_stream" {
		t.Fatalf("app default transport = %q, want runner_stream", app.DefaultTransport)
	}
	if !strings.Contains(app.ConfigSchema, "environment") {
		t.Fatalf("app config schema = %s, want environment property", app.ConfigSchema)
	}
}

func TestValidateDeveloperManifestRejectsAppMentionWithoutRequiredScopes(t *testing.T) {
	_, err := ValidateDeveloperManifest("deploy-app", "third_party", []byte(`{
		"schema_version": "1",
		"slug": "deploy-app",
		"version": "1.2.3",
		"capabilities": {
			"runtime": {
				"runtime": {
					"transports": ["runner_stream"]
				},
				"events": ["agentteam.message.mention"],
				"scopes": ["app.mentions.read"],
				"config_schema": {"type": "object"}
			}
		}
	}`))
	if err == nil || !strings.Contains(err.Error(), "app.mentions.read and messages.write") {
		t.Fatalf("ValidateDeveloperManifest error = %v, want required app scopes error", err)
	}
}

func TestValidateDeveloperManifestRejectsTopLevelAppScopeCapabilityBypass(t *testing.T) {
	_, err := ValidateDeveloperManifest("announcement-app", "third_party", []byte(`{
		"schema_version": "1",
		"slug": "announcement-app",
		"version": "1.0.0",
		"scopes": ["app.mentions.read", "messages.write"],
		"capabilities": {
			"runtime": {
				"runtime": {"transports": ["runner_stream"]},
				"events": [],
				"scopes": ["messages.write", "app.notifications.write"],
				"notifications": {"targets": ["human_dm"], "content": ["message"]}
			}
		}
	}`))
	if err == nil || !strings.Contains(err.Error(), `scope "app.mentions.read" must be declared by capabilities.runtime.scopes`) {
		t.Fatalf("ValidateDeveloperManifest error = %v, want top-level App scope bypass error", err)
	}
}

func TestValidateDeveloperManifestRejectsUnsupportedAppEvent(t *testing.T) {
	_, err := ValidateDeveloperManifest("deploy-app", "third_party", []byte(`{
		"schema_version": "1",
		"slug": "deploy-app",
		"version": "1.2.3",
		"capabilities": {
			"runtime": {
				"runtime": {
					"transports": ["runner_stream"]
				},
				"events": ["agentteam.message.mention", "agentteam.unknown"],
				"scopes": ["app.mentions.read", "messages.write"],
				"config_schema": {"type": "object"}
			}
		}
	}`))
	if err == nil || !strings.Contains(err.Error(), "agentteam.unknown") {
		t.Fatalf("ValidateDeveloperManifest error = %v, want unsupported app event error", err)
	}
}

func TestValidateDeveloperManifestNormalizesAppNotifications(t *testing.T) {
	manifest, err := ValidateDeveloperManifest("approval-app", "third_party", []byte(`{
		"schema_version": "1",
		"slug": "approval-app",
		"version": "1.0.0",
		"capabilities": {
			"runtime": {
				"runtime": {"transports": ["runner_stream"]},
				"events": ["agentteam.app.notification.action"],
				"scopes": ["messages.write", "app.notifications.write", "app.notification_actions.read"],
				"notifications": {
					"targets": ["bound_scope", "human_dm", "bound_scope"],
					"content": ["interactive_card", "message", "interactive_card"]
				}
			}
		}
	}`))
	if err != nil {
		t.Fatalf("ValidateDeveloperManifest returned error: %v", err)
	}
	app, err := ParseStoredAppRuntimeCapability(manifest.Payload)
	if err != nil {
		t.Fatalf("ParseStoredAppRuntimeCapability returned error: %v", err)
	}
	if got, want := app.Events, []string{AppEventNotificationAction}; !reflect.DeepEqual(got, want) {
		t.Fatalf("events = %#v, want %#v", got, want)
	}
	if got, want := app.Scopes, []string{"app.notification_actions.read", "app.notifications.write", "messages.write"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("scopes = %#v, want %#v", got, want)
	}
	if !app.Notifications.Supported {
		t.Fatal("notification capability Supported = false, want true")
	}
	if got, want := app.Notifications.Targets, []string{"bound_scope", "human_dm"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("notification targets = %#v, want %#v", got, want)
	}
	if got, want := app.Notifications.Content, []string{"interactive_card", "message"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("notification content = %#v, want %#v", got, want)
	}
}

func TestValidateDeveloperManifestAllowsMessageOnlyAppNotifications(t *testing.T) {
	_, err := ValidateDeveloperManifest("announcement-app", "third_party", []byte(`{
		"schema_version": "1",
		"slug": "announcement-app",
		"version": "1.0.0",
		"capabilities": {
			"runtime": {
				"runtime": {"transports": ["callback"]},
				"events": [],
				"scopes": ["messages.write", "app.notifications.write"],
				"notifications": {"targets": ["human_dm"], "content": ["message"]}
			}
		}
	}`))
	if err != nil {
		t.Fatalf("message-only notification manifest returned error: %v", err)
	}
}

func TestValidateDeveloperManifestRejectsIncoherentAppNotificationDeclarations(t *testing.T) {
	tests := []struct {
		name    string
		app     string
		wantErr string
	}{
		{
			name:    "unsupported target",
			app:     `"events":[],"scopes":["messages.write","app.notifications.write"],"notifications":{"targets":["everyone"],"content":["message"]}`,
			wantErr: "everyone",
		},
		{
			name:    "notification grant missing",
			app:     `"events":[],"scopes":["messages.write"],"notifications":{"targets":["human_dm"],"content":["message"]}`,
			wantErr: "app.notifications.write and messages.write",
		},
		{
			name:    "action scope missing",
			app:     `"events":["agentteam.app.notification.action"],"scopes":["messages.write","app.notifications.write"],"notifications":{"targets":["human_dm"],"content":["interactive_card"]}`,
			wantErr: "requires app.notification_actions.read",
		},
		{
			name:    "action event without card",
			app:     `"events":["agentteam.app.notification.action"],"scopes":["messages.write","app.notifications.write","app.notification_actions.read"],"notifications":{"targets":["human_dm"],"content":["message"]}`,
			wantErr: "requires interactive_card",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			payload := `{"schema_version":"1","slug":"notification-app","version":"1.0.0","capabilities":{"runtime":{"runtime":{"transports":["runner_stream"]},` + tt.app + `}}}`
			_, err := ValidateDeveloperManifest("notification-app", "third_party", []byte(payload))
			if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
				t.Fatalf("ValidateDeveloperManifest error = %v, want %q", err, tt.wantErr)
			}
		})
	}
}

func TestValidateDeveloperManifestAllowsInteractiveCardsWithoutCallbackActions(t *testing.T) {
	_, err := ValidateDeveloperManifest("status-card-app", "third_party", []byte(`{
		"schema_version":"1",
		"slug":"status-card-app",
		"version":"1.0.0",
		"capabilities":{"runtime":{
			"runtime":{"transports":["runner_stream"]},
			"events":[],
			"scopes":["messages.write","app.notifications.write"],
			"notifications":{"targets":["human_dm"],"content":["interactive_card"]}
		}}
	}`))
	if err != nil {
		t.Fatalf("interactive-card notification manifest returned error: %v", err)
	}
}
