package runner

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/authentication/user"

	"github.com/grafana/grafana-app-sdk/app"
	examplev1 "github.com/grafana/grafana/pkg/services/apiserver/builder/runner/testdata/app/pkg/apis/example/v1"
)

func TestBuilderAdmission_Validate(t *testing.T) {
	exampleObj := &examplev1.Example{
		Spec: examplev1.ExampleSpec{
			A: "test",
		},
	}
	gvk := examplev1.ExampleKind().GroupVersionKind()
	gvr := examplev1.ExampleKind().GroupVersionResource()
	defaultAttributes := admission.NewAttributesRecord(exampleObj, nil, gvk, "default", "foo", gvr, "", admission.Create, nil, false, &user.DefaultInfo{})

	tests := []struct {
		name    string
		app     app.App
		req     *app.AdmissionRequest
		wantErr bool
	}{
		{
			name: "validator exists - success",
			app: &mockApp{
				validateFunc: func(ctx context.Context, req *app.AdmissionRequest) error {
					return nil
				},
			},
			wantErr: false,
		},
		{
			name: "validator exists - error",
			app: &mockApp{
				validateFunc: func(ctx context.Context, req *app.AdmissionRequest) error {
					return errors.New("error")
				},
			},
			wantErr: true,
		},
		{
			name:    "validator not set - success",
			app:     &mockApp{},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			b := appBuilder{app: tt.app}
			err := b.Validate(context.Background(), defaultAttributes, nil)
			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestBuilderAdmission_Mutate(t *testing.T) {
	gvk := examplev1.ExampleKind().GroupVersionKind()
	gvr := examplev1.ExampleKind().GroupVersionResource()
	getAttributes := func() admission.Attributes {
		exampleObj := &examplev1.Example{
			Spec: examplev1.ExampleSpec{
				A: "test",
			},
		}
		return admission.NewAttributesRecord(exampleObj, nil, gvk, "default", "foo", gvr, "", admission.Create, nil, false, &user.DefaultInfo{})
	}
	tests := []struct {
		name       string
		app        app.App
		attributes admission.Attributes
		expected   examplev1.Example
		wantErr    bool
	}{
		{
			name: "mutator exists - success",
			app: &mockApp{
				mutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
					return &app.MutatingResponse{
						UpdatedObject: &examplev1.Example{
							Spec: examplev1.ExampleSpec{
								A: "test",
								B: "mutated",
							},
						},
					}, nil
				},
			},
			attributes: getAttributes(),
			expected: examplev1.Example{
				Spec: examplev1.ExampleSpec{
					A: "test",
					B: "mutated",
				},
			},
			wantErr: false,
		},
		{
			name: "mutator exists - error",
			app: &mockApp{
				mutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
					return nil, errors.New("error")
				},
			},
			attributes: getAttributes(),
			wantErr:    true,
		},
		{
			name:       "mutator not set - no modification",
			app:        &mockApp{},
			attributes: getAttributes(),
			expected: examplev1.Example{
				Spec: examplev1.ExampleSpec{
					A: "test",
				},
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			b := appBuilder{app: tt.app}
			err := b.Mutate(context.Background(), tt.attributes, nil)
			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				require.Equal(t, &tt.expected, tt.attributes.GetObject())
			}
		})
	}
}

type mockApp struct {
	app.App
	mutateFunc   func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error)
	validateFunc func(ctx context.Context, req *app.AdmissionRequest) error
}

func (m *mockApp) Mutate(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
	if m.mutateFunc == nil {
		return nil, app.ErrNotImplemented
	}
	return m.mutateFunc(ctx, req)
}

func (m *mockApp) Validate(ctx context.Context, req *app.AdmissionRequest) error {
	if m.validateFunc == nil {
		return app.ErrNotImplemented
	}
	return m.validateFunc(ctx, req)
}
