package store

import (
	"reflect"
	"testing"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
)

func TestLayeredAccessDoesNotLetAPIKeyExpandBaseline(t *testing.T) {
	models := []PlatformModel{{ID: "model-1", PlatformID: "platform-1"}, {ID: "model-2", PlatformID: "platform-1"}}
	user := &auth.User{GatewayUserID: "user-1", UserGroupID: "group-1", APIKeyID: "key-1"}
	rules := []AccessRule{
		{SubjectType: "user_group", SubjectID: "group-1", ResourceType: "platform_model", ResourceID: "model-1", Effect: "allow"},
		{SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "model-2", Effect: "allow"},
	}
	actual := filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, true), 0)
	if len(actual) != 0 {
		t.Fatalf("api key allow expanded its parent whitelist: %+v", actual)
	}
}

func TestNoRulesInheritAllModels(t *testing.T) {
	models := []PlatformModel{{ID: "model-1"}, {ID: "model-2"}}
	user := &auth.User{GatewayUserID: "user-1", UserGroupID: "group-1", APIKeyID: "key-1"}
	actual := filterPlatformModelsByAccessLayers(models, nil, accessRuleLayers(user, true), 0)
	if !reflect.DeepEqual(actual, models) {
		t.Fatalf("models without rules = %+v, want %+v", actual, models)
	}
}

func TestAPIKeyAllowIsCurrentKeyWhitelistOnly(t *testing.T) {
	models := []PlatformModel{{ID: "model-1"}, {ID: "model-2"}}
	rules := []AccessRule{{
		SubjectType: "api_key", SubjectID: "key-a", ResourceType: "platform_model", ResourceID: "model-1", Effect: "allow",
	}}
	for _, test := range []struct {
		keyID string
		want  []string
	}{{"key-a", []string{"model-1"}}, {"key-b", []string{"model-1", "model-2"}}} {
		user := &auth.User{GatewayUserID: "user-1", APIKeyID: test.keyID}
		actual := filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, true), 0)
		if got := platformModelIDs(actual); !reflect.DeepEqual(got, test.want) {
			t.Fatalf("key %s received %v, want %v", test.keyID, got, test.want)
		}
	}
}

func TestUserGroupAllowDoesNotAffectOtherGroups(t *testing.T) {
	models := []PlatformModel{{ID: "model-1"}, {ID: "model-2"}}
	rules := []AccessRule{{SubjectType: "user_group", SubjectID: "group-a", ResourceType: "platform_model", ResourceID: "model-1", Effect: "allow"}}
	for _, test := range []struct {
		groupID string
		want    []string
	}{{"group-a", []string{"model-1"}}, {"group-b", []string{"model-1", "model-2"}}} {
		user := &auth.User{GatewayUserID: "user-1", UserGroupID: test.groupID}
		actual := filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, false), 0)
		if got := platformModelIDs(actual); !reflect.DeepEqual(got, test.want) {
			t.Fatalf("group %s received %v, want %v", test.groupID, got, test.want)
		}
	}
}

func TestTenantGroupUserAndKeyWhitelistsIntersect(t *testing.T) {
	models := []PlatformModel{{ID: "a"}, {ID: "b"}, {ID: "c"}, {ID: "d"}}
	user := &auth.User{GatewayTenantID: "tenant-1", GatewayUserID: "user-1", UserGroupID: "group-1", APIKeyID: "key-1"}
	rules := []AccessRule{
		{SubjectType: "tenant", SubjectID: "tenant-1", ResourceType: "platform_model", ResourceID: "a", Effect: "allow"},
		{SubjectType: "tenant", SubjectID: "tenant-1", ResourceType: "platform_model", ResourceID: "b", Effect: "allow"},
		{SubjectType: "user_group", SubjectID: "group-1", ResourceType: "platform_model", ResourceID: "b", Effect: "allow"},
		{SubjectType: "user_group", SubjectID: "group-1", ResourceType: "platform_model", ResourceID: "c", Effect: "allow"},
		{SubjectType: "user", SubjectID: "user-1", ResourceType: "platform_model", ResourceID: "b", Effect: "allow"},
		{SubjectType: "user", SubjectID: "user-1", ResourceType: "platform_model", ResourceID: "d", Effect: "allow"},
		{SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "b", Effect: "allow"},
		{SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "c", Effect: "allow"},
	}
	actual := filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, true), 0)
	if got := platformModelIDs(actual); !reflect.DeepEqual(got, []string{"b"}) {
		t.Fatalf("layered whitelist result = %v, want [b]", got)
	}
}

func TestDenyWinsAndPlatformAllowIncludesChildren(t *testing.T) {
	models := []PlatformModel{
		{ID: "allowed", PlatformID: "platform-1"},
		{ID: "denied", PlatformID: "platform-1"},
		{ID: "other", PlatformID: "platform-2"},
	}
	user := &auth.User{GatewayUserID: "user-1", APIKeyID: "key-1"}
	rules := []AccessRule{
		{SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform", ResourceID: "platform-1", Effect: "allow"},
		{SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "denied", Effect: "deny"},
	}
	actual := filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, true), 0)
	if got := platformModelIDs(actual); !reflect.DeepEqual(got, []string{"allowed"}) {
		t.Fatalf("platform allow with child deny = %v, want [allowed]", got)
	}
}

func TestSameLayerAllowsUnionAndAnyDenyWins(t *testing.T) {
	models := []PlatformModel{{ID: "a"}, {ID: "b"}, {ID: "c"}}
	layers := []accessRuleLayer{{subjectType: "user_group", subjectIDs: map[string]bool{"group-a": true, "group-b": true}}}
	rules := []AccessRule{
		{SubjectType: "user_group", SubjectID: "group-a", ResourceType: "platform_model", ResourceID: "a", Effect: "allow"},
		{SubjectType: "user_group", SubjectID: "group-b", ResourceType: "platform_model", ResourceID: "b", Effect: "allow"},
		{SubjectType: "user_group", SubjectID: "group-a", ResourceType: "platform_model", ResourceID: "b", Effect: "deny"},
	}
	actual := filterPlatformModelsByAccessLayers(models, rules, layers, 0)
	if got := platformModelIDs(actual); !reflect.DeepEqual(got, []string{"a"}) {
		t.Fatalf("same-layer allow union and deny result = %v, want [a]", got)
	}
}

func TestBaseModelRuleMatchesEverySource(t *testing.T) {
	models := []PlatformModel{
		{ID: "source-a", BaseModelID: "base-1"},
		{ID: "source-b", BaseModelID: "base-1"},
		{ID: "source-c", BaseModelID: "base-2"},
	}
	user := &auth.User{GatewayUserID: "user-1"}
	rules := []AccessRule{{
		SubjectType: "user", SubjectID: "user-1", ResourceType: "base_model", ResourceID: "base-1", Effect: "allow",
	}}
	actual := filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, false), 0)
	if got := platformModelIDs(actual); !reflect.DeepEqual(got, []string{"source-a", "source-b"}) {
		t.Fatalf("base-model whitelist result = %v, want both base-1 sources", got)
	}
}

func TestAllowPermissionLevelFailsClosed(t *testing.T) {
	models := []PlatformModel{{ID: "model-1"}, {ID: "model-2"}}
	rules := []AccessRule{{
		SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "model-1", Effect: "allow", MinPermissionLevel: 2,
	}}
	user := &auth.User{GatewayUserID: "user-1", APIKeyID: "key-1"}
	if got := filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, true), 1); len(got) != 0 {
		t.Fatalf("insufficient permission inherited models instead of failing closed: %+v", got)
	}
	if got := platformModelIDs(filterPlatformModelsByAccessLayers(models, rules, accessRuleLayers(user, true), 2)); !reflect.DeepEqual(got, []string{"model-1"}) {
		t.Fatalf("sufficient permission result = %v, want [model-1]", got)
	}
}

func TestFilterPlatformModelsByAPIKeyScopesPrunesCapabilities(t *testing.T) {
	models := []PlatformModel{{
		ID:        "model-1",
		ModelType: StringList{"text_generate", "image_generate"},
		Capabilities: map[string]any{
			"text_generate":  map[string]any{"max_context_tokens": 128000},
			"image_generate": map[string]any{"aspect_ratio_allowed": []any{"1:1"}},
			"originalTypes":  []any{"text_generate", "image_generate"},
			"shared":         true,
		},
	}}
	actual := filterPlatformModelsByAPIKeyScopes(models, []string{"image"})
	if len(actual) != 1 || !reflect.DeepEqual(actual[0].ModelType, StringList{"image_generate"}) {
		t.Fatalf("scope-filtered models = %+v", actual)
	}
	if _, exists := actual[0].Capabilities["text_generate"]; exists {
		t.Fatalf("text capability leaked into image scope: %+v", actual[0].Capabilities)
	}
	if _, exists := actual[0].Capabilities["image_generate"]; !exists {
		t.Fatalf("image capability was removed: %+v", actual[0].Capabilities)
	}
	if !reflect.DeepEqual(actual[0].Capabilities["originalTypes"], []string{"image_generate"}) {
		t.Fatalf("originalTypes not pruned: %+v", actual[0].Capabilities["originalTypes"])
	}
	if actual[0].Capabilities["shared"] != true {
		t.Fatalf("shared capability metadata was removed: %+v", actual[0].Capabilities)
	}
}

func TestDiagnoseAPIKeyRulesExplainsEachInactiveLayer(t *testing.T) {
	rules := []AccessRule{
		{ID: "gone", SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "gone", Effect: "allow", Status: "active"},
		{ID: "revoked", SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "revoked", Effect: "allow", Status: "active"},
		{ID: "scope", SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "scope", Effect: "deny", Status: "active"},
		{ID: "effective", SubjectType: "api_key", SubjectID: "key-1", ResourceType: "platform_model", ResourceID: "effective", Effect: "allow", Status: "active"},
	}
	all := []PlatformModel{{ID: "revoked"}, {ID: "scope"}, {ID: "effective"}}
	enabled := append([]PlatformModel(nil), all...)
	baseline := []PlatformModel{{ID: "scope"}, {ID: "effective"}}
	scoped := []PlatformModel{{ID: "effective"}}
	diagnostics := diagnoseAPIKeyRules("key-1", rules, all, enabled, baseline, scoped, nil)
	want := map[string]string{
		"gone": "resource_unavailable", "revoked": "owner_access_revoked", "scope": "scope_not_allowed", "effective": "",
	}
	for _, diagnostic := range diagnostics {
		if diagnostic.Reason != want[diagnostic.RuleID] {
			t.Fatalf("diagnostic %s reason = %q, want %q", diagnostic.RuleID, diagnostic.Reason, want[diagnostic.RuleID])
		}
		if diagnostic.Effective != (diagnostic.RuleID == "effective") {
			t.Fatalf("diagnostic %s effective = %v", diagnostic.RuleID, diagnostic.Effective)
		}
	}
}

func platformModelIDs(models []PlatformModel) []string {
	ids := make([]string, 0, len(models))
	for _, model := range models {
		ids = append(ids, model.ID)
	}
	return ids
}
