package converter

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/app/runtime"
	"github.com/tidbcloud/AgentTeam/internal/store/organization"
	"github.com/tidbcloud/AgentTeam/internal/store/provider"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

func TestBrowserAppResponseConvertersOmitInternalPermissionAndProposalState(t *testing.T) {
	definition := ToAppDefinitionView(registry.AppDefinitionView{
		ID: "app_github",
	})
	definitionJSON, err := json.Marshal(definition)
	require.NoError(t, err)
	for _, forbidden := range []string{`"scopes"`, `"transports"`, `"defaultTransport"`, `"events"`, `"notifications"`} {
		require.NotContains(t, string(definitionJSON), forbidden)
	}
	require.NotContains(t, string(definitionJSON), `"runtimeCapability"`)

	installation := ToAppInstallationResponse(app.AppInstallationView{Grants: []string{"messages.write"}})
	installationJSON, err := json.Marshal(installation)
	require.NoError(t, err)
	require.NotContains(t, string(installationJSON), `"grants"`)

	request := ToAppInstallationRequestResponse(app.AppInstallationRequestView{
		ID:                     "appinstreq_admin_review",
		WorkspaceID:            "wsp_admin_review",
		AppID:                  "app_gitea",
		AppSlug:                "gitea",
		AppName:                "Gitea",
		AvatarURL:              "https://private.example/avatar.png",
		RequestKind:            app.AppInstallationRequestKindConfigure,
		ConnectionTarget:       "private/repository",
		ConnectionID:           "appcxn_private",
		ConnectionLabel:        "Private Repository",
		RequestedConfig:        map[string]any{"instance_url": "https://private.example", "display_name": "Private Gitea"},
		RequestedScopes:        []string{"private.scope"},
		Status:                 app.AppInstallationRequestStatusPending,
		RequestReason:          "private reason",
		RequestedByMemberID:    "mem_private",
		RequestedByUserID:      "usr_private",
		RequestedByDisplayName: "Private Requester",
		DecidedByMemberID:      "mem_decider",
		DecisionReason:         "private decision",
		InstallationID:         "appinst_private",
	})
	requestJSON, err := json.Marshal(request)
	require.NoError(t, err)
	var payload map[string]any
	require.NoError(t, json.Unmarshal(requestJSON, &payload))
	require.Len(t, payload, 3)
	require.Equal(t, "appinstreq_admin_review", payload["id"])
	require.Equal(t, app.AppInstallationRequestKindConfigure, payload["requestKind"])
	require.Equal(t, app.AppInstallationRequestStatusPending, payload["status"])
}

func TestPendingAppInstallationRequestResponseTreatsSetupPatchingAsPending(t *testing.T) {
	items, err := ToPendingAppInstallationRequestResponses([]app.AppInstallationRequestView{{
		ID:     "appinstreq_github_patching",
		AppID:  "app_github",
		Status: app.AppInstallationRequestStatusSetupPatching,
	}})
	require.NoError(t, err)

	require.Len(t, items, 1)
	require.Equal(t, app.AppInstallationRequestStatusPending, items[0].Status)
}

func TestAppResponseConvertersKeepGlobalAndCatalogVisibilitySeparate(t *testing.T) {
	definition := ToAppDefinitionView(registry.AppDefinitionView{
		Visibility:        "unlisted",
		CatalogVisibility: registry.CatalogVisibilityPublic,
	})
	require.Equal(t, "unlisted", definition.Visibility)
	require.Equal(t, registry.CatalogVisibilityPublic, definition.CatalogVisibility)

	installation := ToAppInstallationResponse(app.AppInstallationView{
		CatalogVisibility: registry.CatalogVisibilityPrivate,
	})
	require.Equal(t, registry.CatalogVisibilityPrivate, installation.CatalogVisibility)

	updated := ToAppVisibilityUpdateResponse(app.AppVisibilityUpdateResult{
		CatalogVisibility: registry.CatalogVisibilityPrivate,
	})
	require.Equal(t, registry.CatalogVisibilityPrivate, updated.CatalogVisibility)
}

func assertNilVsEmptySlice[In any, Out any](t *testing.T, name string, convert func([]In) []Out) {
	t.Helper()
	if got := convert(nil); got != nil {
		t.Fatalf("%s(nil) = %#v, want nil", name, got)
	}
	if got := convert([]In{}); got == nil || len(got) != 0 {
		t.Fatalf("%s(empty) = %#v, want non-nil empty slice", name, got)
	}
}

func TestAppResponseConvertersPreserveNilVsEmptySlices(t *testing.T) {
	assertNilVsEmptySlice(t, "ToAppDefinitionViews", ToAppDefinitionViews)
	assertNilVsEmptySlice(t, "ToRuntimeConnectionResponses", ToRuntimeConnectionResponses)
	assertNilVsEmptySlice(t, "ToAppInstallationResponses", ToAppInstallationResponses)
	assertNilVsEmptySlice(t, "ToAppWebhookDeliveryResponses", ToAppWebhookDeliveryResponses)
	assertNilVsEmptySlice(t, "ToMatchedWebhookSubscriptionResponses", ToMatchedWebhookSubscriptionResponses)
	assertNilVsEmptySlice(t, "ToAgentAppAuthorizationResponses", ToAgentAppAuthorizationResponses)
	assertNilVsEmptySlice(t, "ToServiceEventSubscriptionResponses", ToServiceEventSubscriptionResponses)

	// Keep imports anchored to the source packages used by the generic calls above.
	var _ registry.AppDefinitionView
	var _ runtime.ConnectionView
	var _ app.AppInstallationView
}

func TestProviderWorkspaceOrgResponseConvertersPreserveNilVsEmptySlices(t *testing.T) {
	assertNilVsEmptySlice(t, "ToProviderAppConnectionResponses", ToProviderAppConnectionResponses)
	assertNilVsEmptySlice(t, "ToWorkspaceMemberResponses", ToWorkspaceMemberResponses)
	assertNilVsEmptySlice(t, "ToWorkspaceInviteResponses", ToWorkspaceInviteResponses)
	assertNilVsEmptySlice(t, "ToOrgProviderModels", ToOrgProviderModels)
	assertNilVsEmptySlice(t, "ToOrgProviderViews", ToOrgProviderViews)
	assertNilVsEmptySlice(t, "ToOrgProviderTokenViews", ToOrgProviderTokenViews)
	assertNilVsEmptySlice(t, "ToOrgProviderRouteViews", ToOrgProviderRouteViews)
	assertNilVsEmptySlice(t, "ToOrgProviderRouteGroupViews", ToOrgProviderRouteGroupViews)
	assertNilVsEmptySlice(t, "ToOrgWorkspaceProviderGrantViews", ToOrgWorkspaceProviderGrantViews)
	assertNilVsEmptySlice(t, "ToOrgWorkspaceRouteGrantViews", ToOrgWorkspaceRouteGrantViews)
	assertNilVsEmptySlice(t, "ToOrgLLMProxyKeyViews", ToOrgLLMProxyKeyViews)
	assertNilVsEmptySlice(t, "ToOrgLLMStatsSummaryViews", ToOrgLLMStatsSummaryViews)
	assertNilVsEmptySlice(t, "ToOrgLLMStatsDetailViews", ToOrgLLMStatsDetailViews)
	assertNilVsEmptySlice(t, "ToOrgLLMModelStatViews", ToOrgLLMModelStatViews)
	assertNilVsEmptySlice(t, "ToOrgLLMTimeSeriesPoints", ToOrgLLMTimeSeriesPoints)
	assertNilVsEmptySlice(t, "ToOrgLLMErrorTypeStatsViews", ToOrgLLMErrorTypeStatsViews)
	assertNilVsEmptySlice(t, "ToOrgLLMErrorTimeSeriesPoints", ToOrgLLMErrorTimeSeriesPoints)
	assertNilVsEmptySlice(t, "ToOrgLLMQuotaPolicyViews", ToOrgLLMQuotaPolicyViews)

	// Keep imports anchored to the source packages used by the generic calls above.
	var _ provider.AppConnectionView
	var _ workspace.WorkspaceMember
	var _ organization.OrganizationProviderView
}
