package app

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
)

func TestGitHubAppUninstallDeletesWorkspaceProviderCredentials(t *testing.T) {
	t.Parallel()

	t.Run("workspace admin direct uninstall", func(t *testing.T) {
		ctx := context.Background()
		pool := startAppRollbackDB(t, ctx)
		store := New(pool, Options{AppCredentialStorageKey: testAppCredentialStorageKey()})
		admin := seedAppAdminFixture(t, pool, "github_direct_uninstall_cleanup", time.Now().UTC())
		member := seedWorkspaceMemberFixture(t, pool, admin.WorkspaceID, "github_direct_uninstall_cleanup_member", time.Now().UTC())

		_, err := store.FinalizeGitHubAppSetupForWorkspaceAdmin(ctx, admin.UserID, admin.WorkspaceID, validGitHubAppConfigParams())
		require.NoError(t, err)
		installations, err := store.ListAppInstallationsForUser(ctx, admin.UserID, admin.WorkspaceID)
		require.NoError(t, err)
		require.Len(t, installations, 1)
		require.Equal(t, registry.GitHubAppID, installations[0].AppID)

		uninstalled, err := store.UninstallAppInstallationForUser(ctx, admin.UserID, admin.WorkspaceID, installations[0].ID)
		require.NoError(t, err)
		require.False(t, uninstalled.Enabled)
		assertGitHubConfigDeletedAfterUninstall(t, ctx, store, admin.WorkspaceID)

		mode, err := store.GitHubAppSetupModeForUser(ctx, member.UserID, admin.WorkspaceID)
		require.NoError(t, err)
		require.Equal(t, GitHubAppSetupModeRequester, mode, "uninstall must leave ordinary members able to restart setup")
	})

	t.Run("approved member uninstall request", func(t *testing.T) {
		ctx := context.Background()
		pool := startAppRollbackDB(t, ctx)
		store := New(pool, Options{AppCredentialStorageKey: testAppCredentialStorageKey()})
		admin := seedAppAdminFixture(t, pool, "github_approved_uninstall_cleanup", time.Now().UTC())
		member := seedWorkspaceMemberFixture(t, pool, admin.WorkspaceID, "github_approved_uninstall_cleanup_member", time.Now().UTC())

		_, err := store.FinalizeGitHubAppSetupForWorkspaceAdmin(ctx, admin.UserID, admin.WorkspaceID, validGitHubAppConfigParams())
		require.NoError(t, err)
		installations, err := store.ListAppInstallationsForUser(ctx, member.UserID, admin.WorkspaceID)
		require.NoError(t, err)
		require.Len(t, installations, 1)

		request, err := store.RequestAppInstallationForUser(ctx, member.UserID, admin.WorkspaceID, RequestAppInstallationParams{
			InstallationID: installations[0].ID,
			RequestKind:    AppInstallationRequestKindUninstall,
		})
		require.NoError(t, err)
		approved, err := store.ApproveAppInstallationRequestForUser(
			ctx,
			admin.UserID,
			admin.WorkspaceID,
			request.ID,
			DecideAppInstallationRequestParams{},
		)
		require.NoError(t, err)
		require.Equal(t, AppInstallationRequestStatusApproved, approved.Status)
		assertGitHubConfigDeletedAfterUninstall(t, ctx, store, admin.WorkspaceID)

		mode, err := store.GitHubAppSetupModeForUser(ctx, member.UserID, admin.WorkspaceID)
		require.NoError(t, err)
		require.Equal(t, GitHubAppSetupModeRequester, mode)
	})
}

func TestGitHubMemberSetupAfterUninstallWithdrawsOrphanedConnectionApproval(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	store := New(pool, Options{AppCredentialStorageKey: testAppCredentialStorageKey()})
	admin := seedAppAdminFixture(t, pool, "github_uninstall_orphaned_connection", time.Now().UTC())
	member := seedWorkspaceMemberFixture(t, pool, admin.WorkspaceID, "github_uninstall_orphaned_connection_member", time.Now().UTC())

	_, err := store.FinalizeGitHubAppSetupForWorkspaceAdmin(
		ctx, admin.UserID, admin.WorkspaceID, validGitHubAppConfigParams(),
	)
	require.NoError(t, err)
	installations, err := store.ListAppInstallationsForUser(ctx, member.UserID, admin.WorkspaceID)
	require.NoError(t, err)
	require.Len(t, installations, 1)
	config, found, err := store.Provider().LoadGitHubAppConfigForWorkspace(ctx, admin.WorkspaceID)
	require.NoError(t, err)
	require.True(t, found)

	legacyConnect, err := store.RequestVerifiedGitHubConnectionForUser(
		ctx, member.UserID, admin.WorkspaceID, "790", "acme-790", config,
	)
	require.NoError(t, err)
	require.Equal(t, AppInstallationRequestStatusPending, legacyConnect.Status)
	require.Equal(t, AppInstallationRequestKindConnect, legacyConnect.RequestKind)

	uninstall, err := store.RequestAppInstallationForUser(
		ctx, member.UserID, admin.WorkspaceID, RequestAppInstallationParams{
			InstallationID: installations[0].ID,
			RequestKind:    AppInstallationRequestKindUninstall,
		},
	)
	require.NoError(t, err)
	_, err = store.ApproveAppInstallationRequestForUser(
		ctx, admin.UserID, admin.WorkspaceID, uninstall.ID,
		DecideAppInstallationRequestParams{},
	)
	require.NoError(t, err)
	assertGitHubConfigDeletedAfterUninstall(t, ctx, store, admin.WorkspaceID)

	mode, err := store.GitHubAppSetupModeForUser(ctx, member.UserID, admin.WorkspaceID)
	require.NoError(t, err)
	require.Equal(t, GitHubAppSetupModeRequester, mode)

	settled, err := store.GetAppInstallationRequest(ctx, admin.WorkspaceID, legacyConnect.ID)
	require.NoError(t, err)
	require.Equal(t, AppInstallationRequestStatusRejected, settled.Status)
	require.Equal(t, orphanedGitHubConnectionRequestDecisionReason, settled.DecisionReason)
	require.Equal(t, settled.RequestedByMemberID, settled.DecidedByMemberID)
	require.NotNil(t, settled.DecidedAt)
	pending, err := store.ListPendingAppInstallationRequestsForUser(ctx, member.UserID, admin.WorkspaceID)
	require.NoError(t, err)
	require.Empty(t, pending)
}

func TestGitHubGenericConfigureRequestFailsClosedButUninstallRemainsAvailable(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startAppRollbackDB(t, ctx)
	store := New(pool, Options{AppCredentialStorageKey: testAppCredentialStorageKey()})
	admin := seedAppAdminFixture(t, pool, "github_configure_request_guard", time.Now().UTC())
	member := seedWorkspaceMemberFixture(t, pool, admin.WorkspaceID, "github_configure_request_guard_member", time.Now().UTC())

	_, err := store.FinalizeGitHubAppSetupForWorkspaceAdmin(ctx, admin.UserID, admin.WorkspaceID, validGitHubAppConfigParams())
	require.NoError(t, err)
	installations, err := store.ListAppInstallationsForUser(ctx, member.UserID, admin.WorkspaceID)
	require.NoError(t, err)
	require.Len(t, installations, 1)
	require.Equal(t, registry.GitHubAppID, installations[0].AppID)

	for _, testCase := range []struct {
		name        string
		requestKind string
	}{
		{name: "explicit configure", requestKind: AppInstallationRequestKindConfigure},
		{name: "implicit configure", requestKind: ""},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			_, requestErr := store.RequestAppInstallationForUser(ctx, member.UserID, admin.WorkspaceID, RequestAppInstallationParams{
				InstallationID:     installations[0].ID,
				RequestKind:        testCase.requestKind,
				ExpectedBaseConfig: installations[0].Config,
			})
			require.ErrorIs(t, requestErr, ErrBadRequest)
			require.ErrorContains(t, requestErr, "GitHub setup flow")
			require.Equal(t, 0, appInstallationRequestCountForWorkspace(t, ctx, pool, admin.WorkspaceID), "rejected generic configure must not write the request ledger")
		})
	}

	uninstall, err := store.RequestAppInstallationForUser(ctx, member.UserID, admin.WorkspaceID, RequestAppInstallationParams{
		InstallationID: installations[0].ID,
		RequestKind:    AppInstallationRequestKindUninstall,
	})
	require.NoError(t, err)
	require.Equal(t, AppInstallationRequestKindUninstall, uninstall.RequestKind)
	require.Equal(t, AppInstallationRequestStatusPending, uninstall.Status)
}

func assertGitHubConfigDeletedAfterUninstall(t *testing.T, ctx context.Context, store *Store, workspaceID string) {
	t.Helper()
	config, found, err := store.Provider().LoadGitHubAppConfigForWorkspace(ctx, workspaceID)
	require.NoError(t, err)
	require.False(t, found)
	require.Empty(t, config.ID)
}
