package provider

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

func validOptions() Options {
	return Options{
		AuthorizeWorkspaceAccess:         func(context.Context, string, string) error { return nil },
		AuthorizeWorkspaceServiceInstall: func(context.Context, string, string) error { return nil },
		EncryptGitHubSecret:              func(secret string) (string, error) { return "gh:" + secret, nil },
		EncryptOptionalGitHubSecret:      func(secret string) (string, error) { return "gh-opt:" + secret, nil },
		DecryptGitHubSecret:              func(stored string) (string, error) { return "plain:" + stored, nil },
		EncryptGiteaSecret:               func(secret string) (string, error) { return "gt:" + secret, nil },
		DecryptGiteaSecret:               func(stored string) (string, error) { return "plain:" + stored, nil },
		FindAppInstallationByWorkspaceAndApp: func(context.Context, string, string) (StoredAppInstallation, bool, error) {
			return StoredAppInstallation{ID: "install_1", Enabled: true}, true, nil
		},
		UpsertAppConnection: func(context.Context, UpsertAppConnectionParams) (AppConnectionView, error) {
			return AppConnectionView{ID: "conn_1"}, nil
		},
		UpsertAppConnectionInTx: func(context.Context, coredb.Tx, UpsertAppConnectionParams) (AppConnectionView, error) {
			return AppConnectionView{ID: "conn_1"}, nil
		},
		ListAppConnections: func(context.Context, string) ([]AppConnectionView, error) {
			return []AppConnectionView{{ID: "conn_1"}}, nil
		},
		DisableAppConnection: func(context.Context, string, string) error {
			return nil
		},
		DisableAppConnectionInTx: func(context.Context, coredb.Tx, string, string) error {
			return nil
		},
		SetGitHubSubscriptionsBlockedInTx: func(context.Context, coredb.Tx, string, string, bool, time.Time) error {
			return nil
		},
		AppInstallationViewByID: func(context.Context, string, string) (AppInstallationView, error) {
			return AppInstallationView{ID: "install_1"}, nil
		},
	}
}

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

	testCases := []struct {
		name  string
		appID string
		save  func(*Store) error
	}{
		{
			name:  "github",
			appID: "app_github",
			save: func(store *Store) error {
				_, err := store.SaveGitHubAppConfigForWorkspaceAdmin(context.Background(), "usr_1", "wsp_1", UpsertGitHubAppConfigParams{
					PrivateKeyPEM: "private-key", WebhookSecret: "webhook-secret",
				})
				return err
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			for _, state := range []struct {
				name    string
				found   bool
				enabled bool
			}{
				{name: "missing"},
				{name: "disabled", found: true},
			} {
				t.Run(state.name, func(t *testing.T) {
					t.Parallel()
					options := validOptions()
					encryptionCalled := false
					options.FindAppInstallationByWorkspaceAndApp = func(_ context.Context, _ string, appID string) (StoredAppInstallation, bool, error) {
						require.Equal(t, testCase.appID, appID)
						return StoredAppInstallation{ID: "install_1", AppID: appID, Enabled: state.enabled}, state.found, nil
					}
					options.EncryptGitHubSecret = func(string) (string, error) { encryptionCalled = true; return "", nil }
					options.EncryptOptionalGitHubSecret = func(string) (string, error) { encryptionCalled = true; return "", nil }
					options.EncryptGiteaSecret = func(string) (string, error) { encryptionCalled = true; return "", nil }

					err := testCase.save(New(nil, options))
					require.ErrorIs(t, err, ErrConflict)
					require.ErrorContains(t, err, "must be installed")
					require.False(t, encryptionCalled, "provider secrets must not be processed before an active installation exists")
				})
			}
		})
	}
}

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

	testCases := []struct {
		name  string
		appID string
		save  func(*Store) error
	}{
		{
			name:  "github",
			appID: "app_github",
			save: func(store *Store) error {
				_, err := store.SaveGitHubAppConfigForWorkspaceAdmin(context.Background(), "usr_1", "wsp_1", UpsertGitHubAppConfigParams{
					PrivateKeyPEM: "private-key", WebhookSecret: "webhook-secret",
				})
				return err
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			options := validOptions()
			lookups := 0
			options.FindAppInstallationByWorkspaceAndApp = func(_ context.Context, _ string, appID string) (StoredAppInstallation, bool, error) {
				require.Equal(t, testCase.appID, appID)
				lookups++
				return StoredAppInstallation{ID: "install_1", AppID: appID, Enabled: lookups == 1}, true, nil
			}

			err := testCase.save(New(nil, options))
			require.ErrorIs(t, err, ErrConflict)
			require.Equal(t, 2, lookups)
		})
	}
}

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

	require.ErrorContains(t, Options{}.Validate(), "AuthorizeWorkspaceAccess")
	options := validOptions()
	require.NoError(t, options.Validate())
	store := New(nil, options)
	require.NoError(t, store.Validate())

	installation, found, err := store.findAppInstallationByWorkspaceAndApp(context.Background(), "wsp_1", "app_1")
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, "install_1", installation.ID)

	connection, err := store.UpsertAppConnection(context.Background(), UpsertAppConnectionParams{InstallationID: "install_1"})
	require.NoError(t, err)
	require.Equal(t, "conn_1", connection.ID)

	require.NoError(t, store.DisableAppConnection(context.Background(), "install_1", "conn_1"))
}

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

	store := New(nil, Options{})
	require.ErrorContains(t, store.requireWorkspaceAccess(context.Background(), "usr", "wsp"), "authorize workspace access")
	_, err := store.encryptGitHubAppConfigSecret("secret")
	require.ErrorContains(t, err, "github encryption")

	options := validOptions()
	sentinel := errors.New("denied")
	options.AuthorizeWorkspaceAccess = func(context.Context, string, string) error { return sentinel }
	store = New(nil, options)
	require.ErrorIs(t, store.requireWorkspaceAccess(context.Background(), "usr", "wsp"), sentinel)
}

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

	testCases := []struct {
		name        string
		appID       string
		found       bool
		enabled     bool
		apply       func(*Store) error
		wantInError string
	}{
		{
			name:        "github missing",
			appID:       "app_github",
			wantInError: "GitHub App must be installed",
			apply: func(store *Store) error {
				_, base, err := store.PrepareGitHubConnectionForUser(context.Background(), "usr_1", "wsp_1", "123")
				if err != nil {
					return err
				}
				_, err = store.ApplyGitHubAppSetupForUser(context.Background(), "usr_1", "wsp_1", "123", "acme", base)
				return err
			},
		},
		{
			name:        "github disabled",
			appID:       "app_github",
			found:       true,
			wantInError: "GitHub App must be installed",
			apply: func(store *Store) error {
				_, base, err := store.PrepareGitHubConnectionForUser(context.Background(), "usr_1", "wsp_1", "123")
				if err != nil {
					return err
				}
				_, err = store.ApplyGitHubAppSetupForUser(context.Background(), "usr_1", "wsp_1", "123", "acme", base)
				return err
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()

			options := validOptions()
			upsertCalled := false
			options.FindAppInstallationByWorkspaceAndApp = func(_ context.Context, _ string, appID string) (StoredAppInstallation, bool, error) {
				require.Equal(t, testCase.appID, appID)
				return StoredAppInstallation{ID: "install_1", AppID: appID, Enabled: testCase.enabled}, testCase.found, nil
			}
			options.UpsertAppConnection = func(context.Context, UpsertAppConnectionParams) (AppConnectionView, error) {
				upsertCalled = true
				return AppConnectionView{}, nil
			}

			err := testCase.apply(New(nil, options))
			require.ErrorIs(t, err, ErrConflict)
			require.ErrorContains(t, err, testCase.wantInError)
			require.False(t, upsertCalled)
		})
	}
}
