//go:build integration

package execution_test

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/muhlemmer/gu"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"google.golang.org/protobuf/types/known/durationpb"
	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/zitadel/zitadel/internal/integration"
	execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
	object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
)

func TestServer_CreateTarget(t *testing.T) {
	tests := []struct {
		name    string
		ctx     context.Context
		req     *execution.CreateTargetRequest
		want    *execution.CreateTargetResponse
		wantErr bool
	}{
		{
			name: "missing permission",
			ctx:  Tester.WithAuthorization(context.Background(), integration.OrgOwner),
			req: &execution.CreateTargetRequest{
				Name: fmt.Sprint(time.Now().UnixNano() + 1),
			},
			wantErr: true,
		},
		{
			name: "empty name",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name: "",
			},
			wantErr: true,
		},
		{
			name: "empty type",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name:       fmt.Sprint(time.Now().UnixNano() + 1),
				TargetType: nil,
			},
			wantErr: true,
		},
		{
			name: "empty webhook url",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name: fmt.Sprint(time.Now().UnixNano() + 1),
				TargetType: &execution.CreateTargetRequest_RestWebhook{
					RestWebhook: &execution.SetRESTWebhook{},
				},
			},
			wantErr: true,
		},
		{
			name: "empty request response url",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name: fmt.Sprint(time.Now().UnixNano() + 1),
				TargetType: &execution.CreateTargetRequest_RestRequestResponse{
					RestRequestResponse: &execution.SetRESTRequestResponse{},
				},
			},
			wantErr: true,
		},
		{
			name: "empty timeout",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name: fmt.Sprint(time.Now().UnixNano() + 1),
				TargetType: &execution.CreateTargetRequest_RestWebhook{
					RestWebhook: &execution.SetRESTWebhook{
						Url: "https://example.com",
					},
				},
				Timeout:       nil,
				ExecutionType: nil,
			},
			wantErr: true,
		},
		{
			name: "empty execution type, ok",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name: fmt.Sprint(time.Now().UnixNano() + 1),
				TargetType: &execution.CreateTargetRequest_RestWebhook{
					RestWebhook: &execution.SetRESTWebhook{
						Url: "https://example.com",
					},
				},
				Timeout:       durationpb.New(10 * time.Second),
				ExecutionType: nil,
			},
			want: &execution.CreateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
		{
			name: "async execution, ok",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name: fmt.Sprint(time.Now().UnixNano() + 1),
				TargetType: &execution.CreateTargetRequest_RestWebhook{
					RestWebhook: &execution.SetRESTWebhook{
						Url: "https://example.com",
					},
				},
				Timeout: durationpb.New(10 * time.Second),
				ExecutionType: &execution.CreateTargetRequest_IsAsync{
					IsAsync: true,
				},
			},
			want: &execution.CreateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
		{
			name: "interrupt on error execution, ok",
			ctx:  CTX,
			req: &execution.CreateTargetRequest{
				Name: fmt.Sprint(time.Now().UnixNano() + 1),
				TargetType: &execution.CreateTargetRequest_RestWebhook{
					RestWebhook: &execution.SetRESTWebhook{
						Url: "https://example.com",
					},
				},
				Timeout: durationpb.New(10 * time.Second),
				ExecutionType: &execution.CreateTargetRequest_InterruptOnError{
					InterruptOnError: true,
				},
			},
			want: &execution.CreateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := Client.CreateTarget(tt.ctx, tt.req)
			if tt.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)

			integration.AssertDetails(t, tt.want, got)
			assert.NotEmpty(t, got.GetId())
		})
	}
}

func TestServer_UpdateTarget(t *testing.T) {
	type args struct {
		ctx context.Context
		req *execution.UpdateTargetRequest
	}
	tests := []struct {
		name    string
		prepare func(request *execution.UpdateTargetRequest) error
		args    args
		want    *execution.UpdateTargetResponse
		wantErr bool
	}{
		{
			name: "missing permission",
			prepare: func(request *execution.UpdateTargetRequest) error {
				targetID := Tester.CreateTarget(CTX, t).GetId()
				request.TargetId = targetID
				return nil
			},
			args: args{
				ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
				req: &execution.UpdateTargetRequest{
					Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
				},
			},
			wantErr: true,
		},
		{
			name: "not existing",
			prepare: func(request *execution.UpdateTargetRequest) error {
				request.TargetId = "notexisting"
				return nil
			},
			args: args{
				ctx: CTX,
				req: &execution.UpdateTargetRequest{
					Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
				},
			},
			wantErr: true,
		},
		{
			name: "change name, ok",
			prepare: func(request *execution.UpdateTargetRequest) error {
				targetID := Tester.CreateTarget(CTX, t).GetId()
				request.TargetId = targetID
				return nil
			},
			args: args{
				ctx: CTX,
				req: &execution.UpdateTargetRequest{
					Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
				},
			},
			want: &execution.UpdateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
		{
			name: "change type, ok",
			prepare: func(request *execution.UpdateTargetRequest) error {
				targetID := Tester.CreateTarget(CTX, t).GetId()
				request.TargetId = targetID
				return nil
			},
			args: args{
				ctx: CTX,
				req: &execution.UpdateTargetRequest{
					TargetType: &execution.UpdateTargetRequest_RestRequestResponse{
						RestRequestResponse: &execution.SetRESTRequestResponse{
							Url: "https://example.com",
						},
					},
				},
			},
			want: &execution.UpdateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
		{
			name: "change url, ok",
			prepare: func(request *execution.UpdateTargetRequest) error {
				targetID := Tester.CreateTarget(CTX, t).GetId()
				request.TargetId = targetID
				return nil
			},
			args: args{
				ctx: CTX,
				req: &execution.UpdateTargetRequest{
					TargetType: &execution.UpdateTargetRequest_RestWebhook{
						RestWebhook: &execution.SetRESTWebhook{
							Url: "https://example.com/hooks/new",
						},
					},
				},
			},
			want: &execution.UpdateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
		{
			name: "change timeout, ok",
			prepare: func(request *execution.UpdateTargetRequest) error {
				targetID := Tester.CreateTarget(CTX, t).GetId()
				request.TargetId = targetID
				return nil
			},
			args: args{
				ctx: CTX,
				req: &execution.UpdateTargetRequest{
					Timeout: durationpb.New(20 * time.Second),
				},
			},
			want: &execution.UpdateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
		{
			name: "change execution type, ok",
			prepare: func(request *execution.UpdateTargetRequest) error {
				targetID := Tester.CreateTarget(CTX, t).GetId()
				request.TargetId = targetID
				return nil
			},
			args: args{
				ctx: CTX,
				req: &execution.UpdateTargetRequest{
					ExecutionType: &execution.UpdateTargetRequest_IsAsync{
						IsAsync: true,
					},
				},
			},
			want: &execution.UpdateTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.prepare(tt.args.req)
			require.NoError(t, err)

			got, err := Client.UpdateTarget(tt.args.ctx, tt.args.req)
			if tt.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			integration.AssertDetails(t, tt.want, got)
		})
	}
}

func TestServer_DeleteTarget(t *testing.T) {
	target := Tester.CreateTarget(CTX, t)
	tests := []struct {
		name    string
		ctx     context.Context
		req     *execution.DeleteTargetRequest
		want    *execution.DeleteTargetResponse
		wantErr bool
	}{
		{
			name: "missing permission",
			ctx:  Tester.WithAuthorization(context.Background(), integration.OrgOwner),
			req: &execution.DeleteTargetRequest{
				TargetId: target.GetId(),
			},
			wantErr: true,
		},
		{
			name: "empty id",
			ctx:  CTX,
			req: &execution.DeleteTargetRequest{
				TargetId: "",
			},
			wantErr: true,
		},
		{
			name: "delete target",
			ctx:  CTX,
			req: &execution.DeleteTargetRequest{
				TargetId: target.GetId(),
			},
			want: &execution.DeleteTargetResponse{
				Details: &object.Details{
					ChangeDate:    timestamppb.Now(),
					ResourceOwner: Tester.Instance.InstanceID(),
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := Client.DeleteTarget(tt.ctx, tt.req)
			if tt.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			integration.AssertDetails(t, tt.want, got)
		})
	}
}