package authorizer_test

import (
	"context"
	"fmt"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/authorizer"
	influxdbcontext "github.com/influxdata/influxdb/v2/context"
	"github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/kit/platform/errors"
	"github.com/influxdata/influxdb/v2/mock"
	influxdbtesting "github.com/influxdata/influxdb/v2/testing"
	"github.com/stretchr/testify/require"
)

var (
	orgID1 = influxdbtesting.IDPtr(1)
	orgID2 = influxdbtesting.IDPtr(10)
	nbID   = influxdbtesting.IDPtr(2)
)

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

	tests := []struct {
		name          string
		notebookOrg   *platform.ID
		permissionOrg *platform.ID
		wantRet       *influxdb.Notebook
		wantErr       error
	}{
		{
			"authorized to access notebook by id",
			orgID1,
			orgID1,
			newTestNotebook(*orgID1),
			nil,
		},
		{
			"not authorized to access notebook by id",
			orgID1,
			orgID2,
			nil,
			&errors.Error{
				Msg:  fmt.Sprintf("read:orgs/%s/notebooks/%s is unauthorized", orgID1, nbID),
				Code: errors.EUnauthorized,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrlr := gomock.NewController(t)
			svc := mock.NewMockNotebookService(ctrlr)
			s := authorizer.NewNotebookService(svc)

			svc.EXPECT().
				GetNotebook(gomock.Any(), *nbID).
				Return(newTestNotebook(*orgID1), nil)

			perm := newTestNotebooksPermission(influxdb.ReadAction, tt.permissionOrg)

			ctx := context.Background()
			ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
			got, err := s.GetNotebook(ctx, *nbID)
			require.Equal(t, tt.wantErr, err)
			require.Equal(t, tt.wantRet, got)
		})
	}
}

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

	tests := []struct {
		name          string
		notebookOrg   *platform.ID
		permissionOrg *platform.ID
		wantRet       *influxdb.Notebook
		wantErr       error
	}{
		{
			"authorized to create a notebook with the given org",
			orgID1,
			orgID1,
			newTestNotebook(*orgID1),
			nil,
		},
		{
			"not authorized to create a notebook with the given org",
			orgID1,
			orgID2,
			nil,
			&errors.Error{
				Msg:  fmt.Sprintf("write:orgs/%s/notebooks is unauthorized", orgID1),
				Code: errors.EUnauthorized,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrlr := gomock.NewController(t)
			svc := mock.NewMockNotebookService(ctrlr)
			s := authorizer.NewNotebookService(svc)

			perm := newTestNotebooksPermission(influxdb.WriteAction, tt.permissionOrg)
			nb := newTestReqBody(*tt.notebookOrg)

			if tt.wantErr == nil {
				svc.EXPECT().
					CreateNotebook(gomock.Any(), nb).
					Return(tt.wantRet, nil)
			}

			ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
			got, err := s.CreateNotebook(ctx, nb)
			require.Equal(t, tt.wantErr, err)
			require.Equal(t, tt.wantRet, got)
		})
	}
}

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

	tests := []struct {
		name          string
		notebookOrg   *platform.ID
		permissionOrg *platform.ID
		wantRet       *influxdb.Notebook
		wantErr       error
	}{
		{
			"authorized to update notebook by id",
			orgID1,
			orgID1,
			newTestNotebook(*orgID1),
			nil,
		},
		{
			"not authorized to update notebook by id",
			orgID1,
			orgID2,
			nil,
			&errors.Error{
				Msg:  fmt.Sprintf("write:orgs/%s/notebooks/%s is unauthorized", orgID1, nbID),
				Code: errors.EUnauthorized,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrlr := gomock.NewController(t)
			svc := mock.NewMockNotebookService(ctrlr)
			s := authorizer.NewNotebookService(svc)

			svc.EXPECT().
				GetNotebook(gomock.Any(), *nbID).
				Return(newTestNotebook(*tt.notebookOrg), nil)

			perm := newTestNotebooksPermission(influxdb.WriteAction, tt.permissionOrg)
			nb := newTestReqBody(*tt.notebookOrg)

			if tt.wantErr == nil {
				svc.EXPECT().
					UpdateNotebook(gomock.Any(), *nbID, nb).
					Return(tt.wantRet, nil)
			}

			ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
			got, err := s.UpdateNotebook(ctx, *nbID, nb)
			require.Equal(t, tt.wantErr, err)
			require.Equal(t, tt.wantRet, got)
		})
	}
}

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

	tests := []struct {
		name          string
		notebookOrg   *platform.ID
		permissionOrg *platform.ID
		wantErr       error
	}{
		{
			"authorized to delete notebook by id",
			orgID1,
			orgID1,
			nil,
		},
		{
			"not authorized to delete notebook by id",
			orgID1,
			orgID2,
			&errors.Error{
				Msg:  fmt.Sprintf("write:orgs/%s/notebooks/%s is unauthorized", orgID1, nbID),
				Code: errors.EUnauthorized,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrlr := gomock.NewController(t)
			svc := mock.NewMockNotebookService(ctrlr)
			s := authorizer.NewNotebookService(svc)

			svc.EXPECT().
				GetNotebook(gomock.Any(), *nbID).
				Return(newTestNotebook(*tt.notebookOrg), nil)

			perm := newTestNotebooksPermission(influxdb.WriteAction, tt.permissionOrg)

			if tt.wantErr == nil {
				svc.EXPECT().
					DeleteNotebook(gomock.Any(), *nbID).
					Return(nil)
			}

			ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
			got := s.DeleteNotebook(ctx, *nbID)
			require.Equal(t, tt.wantErr, got)
		})
	}
}

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

	tests := []struct {
		name          string
		notebookOrg   *platform.ID
		permissionOrg *platform.ID
		wantRet       []*influxdb.Notebook
		wantErr       error
	}{
		{
			"authorized to list notebooks for the specified org",
			orgID1,
			orgID1,
			[]*influxdb.Notebook{},
			nil,
		},
		{
			"not authorized to list notebooks for the specified org",
			orgID1,
			orgID2,
			nil,
			&errors.Error{
				Msg:  fmt.Sprintf("read:orgs/%s/notebooks is unauthorized", orgID1),
				Code: errors.EUnauthorized,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrlr := gomock.NewController(t)
			svc := mock.NewMockNotebookService(ctrlr)
			s := authorizer.NewNotebookService(svc)

			perm := newTestNotebooksPermission(influxdb.ReadAction, tt.permissionOrg)
			filter := influxdb.NotebookListFilter{OrgID: *tt.notebookOrg}

			if tt.wantErr == nil {
				svc.EXPECT().
					ListNotebooks(gomock.Any(), filter).
					Return(tt.wantRet, nil)
			}

			ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
			got, err := s.ListNotebooks(ctx, filter)
			require.Equal(t, tt.wantErr, err)
			require.Equal(t, tt.wantRet, got)
		})
	}
}

func newTestNotebook(orgID platform.ID) *influxdb.Notebook {
	return &influxdb.Notebook{
		OrgID: orgID,
		ID:    *nbID,
		Name:  "test notebook",
		Spec: influxdb.NotebookSpec{
			"hello": "goodbye",
		},
	}
}

func newTestReqBody(orgID platform.ID) *influxdb.NotebookReqBody {
	return &influxdb.NotebookReqBody{
		OrgID: orgID,
		Name:  "testing",
		Spec: influxdb.NotebookSpec{
			"hello": "goodbye",
		},
	}
}

func newTestNotebooksPermission(action influxdb.Action, orgID *platform.ID) influxdb.Permission {
	return influxdb.Permission{
		Action: action,
		Resource: influxdb.Resource{
			Type:  influxdb.NotebooksResourceType,
			OrgID: orgID,
		},
	}
}
