package exec

import (
	"context"
	"encoding/json"
	"slices"
	"testing"

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

func TestTeamActionSchemasExposeOnlyMinimalFields(t *testing.T) {
	list, err := describe(teamListSpec)
	require.NoError(t, err)
	get, err := describe(teamGetSpec)
	require.NoError(t, err)
	update, err := describe(teamUpdateSpec)
	require.NoError(t, err)

	require.Equal(t, []string{"cursor", "limit", "query"}, schemaPropertyNames(t, list.InputSchema))
	require.Equal(t, []string{"hasMore", "nextCursor", "teams"}, outputDataPropertyNames(t, list.OutputSchema))
	require.Equal(t, []string{"teamId"}, schemaPropertyNames(t, get.InputSchema))
	require.Equal(t, []string{"team"}, outputDataPropertyNames(t, get.OutputSchema))
	require.Equal(t, []string{"description", "memberUpdates", "requestId", "teamId"}, schemaPropertyNames(t, update.InputSchema))
	require.Equal(t, []string{"status"}, outputDataPropertyNames(t, update.OutputSchema))

	schema := mustCompile(t, update.InputSchema)
	require.NoError(t, validateInstance(schema, json.RawMessage(`{
		"teamId":"team_0123456789abcd","requestId":"request-1","description":"Ship safely",
		"memberUpdates":[{"teamMemberId":"wtm_1","roleLabel":"Reviewer","roleOverlayPrompt":"Review APIs","wakeOnTeamMention":false}]
	}`)))
	for _, forbidden := range []string{"role", "members", "addMembers", "removeMembers", "leaderId", "sortOrder", "version", "approvalId"} {
		require.Error(t, validateInstance(schema, json.RawMessage(`{"teamId":"team_0123456789abcd","requestId":"request-1","description":"x","`+forbidden+`":"x"}`)), forbidden)
	}
}

func TestTeamUpdatePreservesExplicitClearAndFalse(t *testing.T) {
	empty := ""
	wake := false
	raw, err := json.Marshal(teamUpdateInput{
		TeamID: "team_0123456789abcd", RequestID: "request-1",
		MemberUpdates: []teamMemberUpdate{{
			TeamMemberID: "wtm_1", RoleLabel: &empty, RoleOverlayPrompt: &empty, WakeOnTeamMention: &wake,
		}},
	})
	require.NoError(t, err)
	require.JSONEq(t, `{"teamId":"team_0123456789abcd","requestId":"request-1","memberUpdates":[{"teamMemberId":"wtm_1","roleLabel":"","roleOverlayPrompt":"","wakeOnTeamMention":false}]}`, string(raw))
}

func TestTeamHandlersCallRuntimeAndReturnMinimalOutput(t *testing.T) {
	remote := &fakeTeamRemote{
		listResult:   teamListOutput{Teams: []teamListItem{{TeamID: "team_0123456789abcd", Name: "Core Team", Handle: "core", Description: "Core duties", Role: "leader"}}},
		getResult:    teamGetOutput{Team: teamDetail{TeamID: "team_0123456789abcd", Name: "Core Team", Description: "Core duties"}},
		updateResult: teamUpdateOutput{Status: "pending"},
	}
	handler := teamHandler{remote: remote}

	listed, err := handler.list(context.Background(), actionScopedJSON[teamListInput]{Body: teamListInput{}})
	require.NoError(t, err)
	require.Equal(t, "team_0123456789abcd", listed.Data.Teams[0].TeamID)
	listedJSON, err := json.Marshal(listed.Data.Teams[0])
	require.NoError(t, err)
	require.JSONEq(t, `{"teamId":"team_0123456789abcd","name":"Core Team","handle":"core","description":"Core duties","role":"leader"}`, string(listedJSON))

	got, err := handler.get(context.Background(), actionScopedJSON[teamGetInput]{Body: teamGetInput{TeamID: "team_0123456789abcd"}})
	require.NoError(t, err)
	require.Equal(t, "Core duties", got.Data.Team.Description)
	gotJSON, err := json.Marshal(got.Data.Team)
	require.NoError(t, err)
	require.JSONEq(t, `{"teamId":"team_0123456789abcd","name":"Core Team","description":"Core duties","members":[]}`, string(gotJSON))

	description := "New duties"
	updated, err := handler.update(context.Background(), actionScopedJSON[teamUpdateInput]{Body: teamUpdateInput{TeamID: "team_0123456789abcd", RequestID: "request-1", Description: &description}})
	require.NoError(t, err)
	require.Equal(t, "pending", updated.Data.Status)
	require.Equal(t, "team_0123456789abcd", remote.updatedTeamID)
}

type fakeTeamRemote struct {
	listResult    teamListOutput
	getResult     teamGetOutput
	updateResult  teamUpdateOutput
	updatedTeamID string
}

func (f *fakeTeamRemote) list(context.Context, teamListInput) (teamListOutput, error) {
	return f.listResult, nil
}
func (f *fakeTeamRemote) get(context.Context, string) (teamGetOutput, error) { return f.getResult, nil }
func (f *fakeTeamRemote) update(_ context.Context, teamID string, input teamUpdateRequest) (teamUpdateOutput, error) {
	f.updatedTeamID = teamID
	return f.updateResult, nil
}

func schemaPropertyNames(t *testing.T, raw json.RawMessage) []string {
	t.Helper()
	var schema struct {
		Properties map[string]json.RawMessage `json:"properties"`
	}
	require.NoError(t, json.Unmarshal(raw, &schema))
	return sortedMapKeys(schema.Properties)
}

func outputDataPropertyNames(t *testing.T, raw json.RawMessage) []string {
	t.Helper()
	var schema struct {
		Properties map[string]json.RawMessage `json:"properties"`
	}
	require.NoError(t, json.Unmarshal(raw, &schema))
	var data struct {
		Properties map[string]json.RawMessage `json:"properties"`
	}
	require.NoError(t, json.Unmarshal(schema.Properties["data"], &data))
	return sortedMapKeys(data.Properties)
}

func sortedMapKeys[V any](values map[string]V) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	slices.Sort(keys)
	return keys
}

var _ teamRuntime = (*fakeTeamRemote)(nil)
