package all

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

	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/kv"
	"github.com/influxdata/influxdb/v2/snowflake"
	"github.com/stretchr/testify/require"
)

func TestMigration_AnnotationsNotebooksOperToken(t *testing.T) {
	ctx, cancelFunc := context.WithCancel(context.Background())
	defer cancelFunc()

	// Run up to migration 15.
	ts := newService(t, ctx, 15)

	// Auth bucket contains the authorizations AKA tokens
	authBucket := []byte("authorizationsv1")

	// The store returned by newService will include an operator token with the
	// current system's entire list of resources already, so remove that before
	// proceeding with the tests.
	err := ts.Store.Update(context.Background(), func(tx kv.Tx) error {
		bkt, err := tx.Bucket(authBucket)
		require.NoError(t, err)

		cursor, err := bkt.ForwardCursor(nil)
		require.NoError(t, err)

		return kv.WalkCursor(ctx, cursor, func(k, _ []byte) (bool, error) {
			err := bkt.Delete(k)
			require.NoError(t, err)
			return true, nil
		})
	})
	require.NoError(t, err)

	// Verify that running the migration in the absence of an operator token will
	// not crash influxdb.
	require.NoError(t, Migration0016_AddAnnotationsNotebooksToOperToken.Up(context.Background(), ts.Store))

	// Seed some authorizations
	id1 := snowflake.NewIDGenerator().ID()
	id2 := snowflake.NewIDGenerator().ID()
	OrgID := ts.Org.ID
	UserID := ts.User.ID

	auths := []influxdb.Authorization{
		{
			ID:          id1, // a non-operator token
			OrgID:       OrgID,
			UserID:      UserID,
			Permissions: permsShouldNotChange(),
		},
		{
			ID:          id2, // an operator token
			OrgID:       OrgID,
			UserID:      UserID,
			Permissions: preNotebooksAnnotationsOpPerms(),
		},
	}

	for _, a := range auths {
		js, err := json.Marshal(a)
		require.NoError(t, err)
		idBytes, err := a.ID.Encode()
		require.NoError(t, err)

		err = ts.Store.Update(context.Background(), func(tx kv.Tx) error {
			bkt, err := tx.Bucket(authBucket)
			require.NoError(t, err)
			return bkt.Put(idBytes, js)
		})
		require.NoError(t, err)
	}

	encoded1, err := id1.Encode()
	require.NoError(t, err)
	encoded2, err := id2.Encode()
	require.NoError(t, err)

	checkPerms := func(expectedOpPerms []influxdb.Permission) {
		// the first item should never change
		err = ts.Store.View(context.Background(), func(tx kv.Tx) error {
			bkt, err := tx.Bucket(authBucket)
			require.NoError(t, err)

			b, err := bkt.Get(encoded1)
			require.NoError(t, err)

			var token influxdb.Authorization
			require.NoError(t, json.Unmarshal(b, &token))
			require.Equal(t, auths[0], token)

			return nil
		})
		require.NoError(t, err)

		// the second item is the 2.0.x operator token and should have been updated to match our expectations
		err = ts.Store.View(context.Background(), func(tx kv.Tx) error {
			bkt, err := tx.Bucket(authBucket)
			require.NoError(t, err)

			b, err := bkt.Get(encoded2)
			require.NoError(t, err)

			var token influxdb.Authorization
			require.NoError(t, json.Unmarshal(b, &token))

			require.ElementsMatch(t, expectedOpPerms, token.Permissions)
			return nil
		})
		require.NoError(t, err)
	}

	// Test applying the migration for the 1st time.
	require.NoError(t, Migration0016_AddAnnotationsNotebooksToOperToken.Up(context.Background(), ts.Store))
	checkPerms(append(preNotebooksAnnotationsOpPerms(), notebooksAndAnnotationsPerms(0)...))

	// Downgrade the migration.
	require.NoError(t, Migration0016_AddAnnotationsNotebooksToOperToken.Down(context.Background(), ts.Store))
	checkPerms(preNotebooksAnnotationsOpPerms())

	// Test re-applying the migration after a downgrade.
	require.NoError(t, Migration0016_AddAnnotationsNotebooksToOperToken.Up(context.Background(), ts.Store))
	checkPerms(append(preNotebooksAnnotationsOpPerms(), notebooksAndAnnotationsPerms(0)...))
}

func Test_PermListsMatch(t *testing.T) {
	tests := []struct {
		name string
		l1   []influxdb.Permission
		l2   []influxdb.Permission
		want bool
	}{
		{
			"empty lists",
			[]influxdb.Permission{},
			[]influxdb.Permission{},
			true,
		},
		{
			"not matching",
			[]influxdb.Permission{
				{
					Action: influxdb.ReadAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
			},
			[]influxdb.Permission{
				{
					Action: influxdb.WriteAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
			},
			false,
		},
		{
			"matches same order",
			[]influxdb.Permission{
				{
					Action: influxdb.ReadAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
				{
					Action: influxdb.WriteAction,
					Resource: influxdb.Resource{
						Type: influxdb.DashboardsResourceType,
					},
				},
			},
			[]influxdb.Permission{
				{
					Action: influxdb.ReadAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
				{
					Action: influxdb.WriteAction,
					Resource: influxdb.Resource{
						Type: influxdb.DashboardsResourceType,
					},
				},
			},
			true,
		},
		{
			"matches different order",
			[]influxdb.Permission{
				{
					Action: influxdb.ReadAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
				{
					Action: influxdb.WriteAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
				{
					Action: influxdb.WriteAction,
					Resource: influxdb.Resource{
						Type: influxdb.DBRPResourceType,
					},
				},
				{
					Action: influxdb.ReadAction,
					Resource: influxdb.Resource{
						Type: influxdb.DBRPResourceType,
					},
				},
			},
			[]influxdb.Permission{
				{
					Action: influxdb.WriteAction,
					Resource: influxdb.Resource{
						Type: influxdb.DBRPResourceType,
					},
				},
				{
					Action: influxdb.ReadAction,
					Resource: influxdb.Resource{
						Type: influxdb.DBRPResourceType,
					},
				},
				{
					Action: influxdb.WriteAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
				{
					Action: influxdb.ReadAction,
					Resource: influxdb.Resource{
						Type: influxdb.ChecksResourceType,
					},
				},
			},
			true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := permListsMatch(tt.l1, tt.l2)
			require.Equal(t, tt.want, got)
		})
	}
}

// This set of permissions shouldn't change - it doesn't match the operator
// token.
func permsShouldNotChange() []influxdb.Permission {
	return []influxdb.Permission{
		{
			Action: influxdb.ReadAction,
			Resource: influxdb.Resource{
				Type: influxdb.ChecksResourceType,
			},
		},
		{
			Action: influxdb.WriteAction,
			Resource: influxdb.Resource{
				Type: influxdb.ChecksResourceType,
			},
		},
	}
}
