package extensionflow

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/extension/spec"
)

func TestCatalogStateIsAndTracksContentIdentity(t *testing.T) {
	defA := registry.Definition{
		Domain: "tools", Version: 1, Digest: "sha256:aaa",
		Actions: []registry.DefinitionAction{
			{Name: "list-items", Safety: spec.Safety{Class: "read"}},
			{Name: "create-item", Safety: spec.Safety{Class: "mutation"}},
		},
	}
	defB := registry.Definition{
		Domain: "notes", Version: 2, Digest: "sha256:bbb",
		Actions: []registry.DefinitionAction{{
			Name: "read-note", Safety: spec.Safety{Class: "read"},
		}},
	}
	entry := func(name string, definition registry.Definition, actions []string) catalogEntry {
		t.Helper()
		value, err := catalogEntryFromAuthorization(name, definition, actions)
		require.NoError(t, err)
		return value
	}
	catalog := []catalogEntry{
		entry("tools", defA, []string{"create-item", "list-items"}),
		entry("notes", defB, []string{"read-note"}),
	}

	state, err := marshalCatalogState(catalog)
	require.NoError(t, err)
	reordered, err := marshalCatalogState([]catalogEntry{
		entry("notes", defB, []string{"read-note"}),
		entry("tools", defA, []string{"list-items", "create-item"}),
	})
	require.NoError(t, err)
	require.Equal(t, state, reordered)

	mutations := []struct {
		name   string
		mutate func([]catalogEntry)
	}{
		{"extension name", func(entries []catalogEntry) {
			entries[0].ExtensionName = "tools-2"
		}},
		{"definition domain", func(entries []catalogEntry) {
			entries[0].DefinitionDomain = "tools-2"
		}},
		{"definition version", func(entries []catalogEntry) {
			entries[0].DefinitionVersion++
		}},
		{"definition digest", func(entries []catalogEntry) {
			entries[0].DefinitionDigest = "sha256:changed"
		}},
		{"action set", func(entries []catalogEntry) {
			entries[0].Actions = entries[0].Actions[:1]
		}},
		{"safety class", func(entries []catalogEntry) {
			entries[0].Actions[0].Safety.Class = "destructive"
		}},
	}
	for _, test := range mutations {
		t.Run(test.name, func(t *testing.T) {
			changed := cloneCatalogEntries(catalog)
			test.mutate(changed)
			next, err := marshalCatalogState(changed)
			require.NoError(t, err)
			require.NotEqual(t, state, next)
		})
	}

	presentationOnly := cloneCatalogEntries(catalog)
	presentationOnly[0].Actions[0].Safety.Idempotent = true
	presentationOnly[0].Actions[0].Safety.Destructive = true
	unchanged, err := marshalCatalogState(presentationOnly)
	require.NoError(t, err)
	require.Equal(t, state, unchanged)
}

func TestCatalogStateEmptyAndFailClosed(t *testing.T) {
	empty, err := marshalCatalogState(nil)
	require.NoError(t, err)
	require.Equal(t, "[]", empty)

	base := catalogEntry{
		ExtensionName: "tools", DefinitionDomain: "tools",
		DefinitionVersion: 1, DefinitionDigest: "sha256:aaa",
		Actions: []catalogAction{{
			Name: "list-items", Safety: spec.Safety{Class: "read"},
		}},
	}
	for name, entries := range map[string][]catalogEntry{
		"duplicate extension": {base, base},
		"blank extension":     {{DefinitionDomain: "tools", DefinitionVersion: 1, DefinitionDigest: "sha256:aaa"}},
		"missing domain":      {{ExtensionName: "tools", DefinitionVersion: 1, DefinitionDigest: "sha256:aaa"}},
		"missing version":     {{ExtensionName: "tools", DefinitionDomain: "tools", DefinitionDigest: "sha256:aaa"}},
		"missing digest":      {{ExtensionName: "tools", DefinitionDomain: "tools", DefinitionVersion: 1}},
	} {
		t.Run(name, func(t *testing.T) {
			_, err := marshalCatalogState(entries)
			require.Error(t, err)
		})
	}
	duplicateAction := base
	duplicateAction.Actions = append(
		duplicateAction.Actions,
		duplicateAction.Actions[0],
	)
	_, err = marshalCatalogState([]catalogEntry{duplicateAction})
	require.Error(t, err)

	blankAction := base
	blankAction.Actions[0].Name = ""
	_, err = marshalCatalogState([]catalogEntry{blankAction})
	require.Error(t, err)
}

func TestCatalogEntryFromAuthorizationFailClosed(t *testing.T) {
	definition := registry.Definition{
		Domain: "tools", Version: 1, Digest: "sha256:aaa",
		Actions: []registry.DefinitionAction{{
			Name: "list-items", Safety: spec.Safety{Class: "read"},
		}},
	}
	for name, test := range map[string]struct {
		extensionName string
		actions       []string
	}{
		"blank extension": {"", []string{"list-items"}},
		"blank action":    {"tools", []string{""}},
		"unknown action":  {"tools", []string{"ghost"}},
		"duplicate action": {
			"tools", []string{"list-items", "list-items"},
		},
	} {
		t.Run(name, func(t *testing.T) {
			_, err := catalogEntryFromAuthorization(
				test.extensionName,
				definition,
				test.actions,
			)
			require.Error(t, err)
		})
	}
}

func TestMarshalCatalogStateDoesNotMutateInput(t *testing.T) {
	input := []catalogEntry{{
		ExtensionName: "tools", DefinitionDomain: "tools",
		DefinitionVersion: 1, DefinitionDigest: "sha256:aaa",
		Actions: []catalogAction{
			{Name: "z", Safety: spec.Safety{Class: "read"}},
			{Name: "a", Safety: spec.Safety{Class: "read"}},
		},
	}}
	before := []string{input[0].Actions[0].Name, input[0].Actions[1].Name}
	_, err := marshalCatalogState(input)
	require.NoError(t, err)
	assert.Equal(
		t,
		before,
		[]string{input[0].Actions[0].Name, input[0].Actions[1].Name},
	)
}

func cloneCatalogEntries(entries []catalogEntry) []catalogEntry {
	cloned := make([]catalogEntry, len(entries))
	for index, entry := range entries {
		cloned[index] = entry
		cloned[index].Actions = append([]catalogAction(nil), entry.Actions...)
	}
	return cloned
}
