package agentnetwork

import (
	"context"
	"runtime"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/netbirdio/netbird/management/internals/modules/agentnetwork/types"
	"github.com/netbirdio/netbird/management/server/account"
	"github.com/netbirdio/netbird/management/server/permissions"
	"github.com/netbirdio/netbird/management/server/permissions/modules"
	"github.com/netbirdio/netbird/management/server/permissions/operations"
	"github.com/netbirdio/netbird/management/server/store"
	nbtypes "github.com/netbirdio/netbird/management/server/types"
	"github.com/netbirdio/netbird/shared/management/status"
)

// bootstrapFixture wires a real sqlite store to a gomock permissions manager
// so tests can grant the provider permission while denying (or never
// expecting) the settings one.
type bootstrapFixture struct {
	manager Manager
	store   store.Store
	perms   *permissions.MockManager
}

func newBootstrapFixture(t *testing.T) *bootstrapFixture {
	t.Helper()
	if runtime.GOOS == "windows" {
		t.Skip("sqlite store not properly supported on Windows yet")
	}
	t.Setenv("NETBIRD_STORE_ENGINE", string(nbtypes.SqliteStoreEngine))

	st, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
	require.NoError(t, err, "test store setup must succeed")
	t.Cleanup(cleanUp)

	ctrl := gomock.NewController(t)
	perms := permissions.NewMockManager(ctrl)

	accounts := account.NewMockManager(ctrl)
	accounts.EXPECT().StoreEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
	accounts.EXPECT().UpdateAccountPeers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
	accounts.EXPECT().BufferUpdateAccountPeers(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()

	return &bootstrapFixture{
		manager: NewManager(st, perms, accounts, nil),
		store:   st,
		perms:   perms,
	}
}

func (f *bootstrapFixture) expectPermission(accountID, userID string, module modules.Module, op operations.Operation, allowed bool) {
	f.perms.EXPECT().
		ValidateUserPermissions(gomock.Any(), accountID, userID, module, op).
		Return(allowed, context.Background(), nil)
}

func newBootstrapProvider(accountID string) *types.Provider {
	p := types.NewProvider(accountID)
	p.Name = "openai"
	p.UpstreamURL = "https://api.openai.com"
	p.APIKey = "sk-test"
	p.Enabled = true
	return p
}

// TestCreateProviderBootstrapRequiresSettingsPermission pins the gate on the
// one-time settings bootstrap: creating the first provider with a
// bootstrap_cluster pins the account's cluster and subdomain, which is a
// settings write and must not ride on the providers permission alone.
func TestCreateProviderBootstrapRequiresSettingsPermission(t *testing.T) {
	ctx := context.Background()

	t.Run("denied without settings permission", func(t *testing.T) {
		f := newBootstrapFixture(t)
		f.expectPermission("account1", "user1", modules.AgentNetworkProviders, operations.Create, true)
		f.expectPermission("account1", "user1", modules.AgentNetworkSettings, operations.Create, false)

		_, err := f.manager.CreateProvider(ctx, "user1", newBootstrapProvider("account1"), "cluster1.example.com")
		require.Error(t, err, "bootstrap without settings permission must fail")
		var sErr *status.Error
		require.ErrorAs(t, err, &sErr)
		assert.Equal(t, status.PermissionDenied, sErr.Type(), "denial should surface as permission denied")

		providers, err := f.store.GetAccountAgentNetworkProviders(ctx, store.LockingStrengthNone, "account1")
		require.NoError(t, err)
		assert.Empty(t, providers, "provider must not be persisted when bootstrap is denied")
		_, err = f.store.GetAgentNetworkSettings(ctx, store.LockingStrengthNone, "account1")
		assert.Error(t, err, "settings row must not be created when bootstrap is denied")
	})

	t.Run("allowed with settings permission", func(t *testing.T) {
		f := newBootstrapFixture(t)
		f.expectPermission("account1", "user1", modules.AgentNetworkProviders, operations.Create, true)
		f.expectPermission("account1", "user1", modules.AgentNetworkSettings, operations.Create, true)

		created, err := f.manager.CreateProvider(ctx, "user1", newBootstrapProvider("account1"), "cluster1.example.com")
		require.NoError(t, err, "bootstrap with both permissions must succeed")
		require.NotNil(t, created)

		settings, err := f.store.GetAgentNetworkSettings(ctx, store.LockingStrengthNone, "account1")
		require.NoError(t, err, "bootstrap must create the settings row")
		assert.Equal(t, "cluster1.example.com", settings.Cluster, "settings should pin the bootstrap cluster")
	})

	t.Run("existing settings need no settings permission", func(t *testing.T) {
		f := newBootstrapFixture(t)
		require.NoError(t, f.store.SaveAgentNetworkSettings(ctx, &types.Settings{
			AccountID: "account1",
			Cluster:   "cluster1.example.com",
			Subdomain: "existing",
		}), "pre-existing settings row setup must succeed")

		// Only the providers permission may be consulted: gomock fails the
		// test on any unexpected settings-permission call.
		f.expectPermission("account1", "user1", modules.AgentNetworkProviders, operations.Create, true)

		_, err := f.manager.CreateProvider(ctx, "user1", newBootstrapProvider("account1"), "cluster1.example.com")
		require.NoError(t, err, "create with existing settings must not require the settings permission")
	})

	t.Run("no bootstrap cluster needs no settings permission", func(t *testing.T) {
		f := newBootstrapFixture(t)
		f.expectPermission("account1", "user1", modules.AgentNetworkProviders, operations.Create, true)

		_, err := f.manager.CreateProvider(ctx, "user1", newBootstrapProvider("account1"), "")
		require.NoError(t, err, "create without bootstrap must not require the settings permission")
	})
}
