package handler

import (
	"bytes"
	"encoding/json"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"

	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/common/model/rms/request"
	"github.com/pingcap/test-infra/services/rms/log"
	"github.com/pingcap/test-infra/services/rms/mocks"
	"github.com/pingcap/test-infra/services/rms/utils"
)

func setupUnitTest() {
	h = NewHandler(&mocks.ServiceInterface{}, &mocks.StatisticsService{})
	e = NewHTTPServer(h)
	log.Log = &log.Logger{Sugar: zap.NewNop().Sugar()} // suppress log
}

func TestValidate(t *testing.T) {
	setupUnitTest()

	type PathParams map[string]string

	type testCase struct {
		name        string
		httpMethod  string // actually setting method or not make not difference
		apiPath     string // doesn't matter either
		queryString string
		pathParams  PathParams
		dataStruct  interface{}
		handleFunc  echo.HandlerFunc
	}

	cases := []testCase{
		{
			name:       "RenewLease empty Name",
			httpMethod: echo.PUT, apiPath: "/api/v1/testbeds/:name/lease/renew", handleFunc: h.RenewLease,
			pathParams:  map[string]string{"name": ""},
			queryString: "?lease=1h",
		},
		{
			name:       "RenewLease invalid Lease",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds/:name/renew", handleFunc: h.RenewLease,
			pathParams:  map[string]string{"name": "ut"},
			queryString: "?lease=invalid",
		},
		{
			name:       "CreateTestbed empty TestBed.Name and TestBed.GenerateName",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds", handleFunc: h.CreateTestbed,
			dataStruct: request.CreateTestbedRequest{TestBed: model.TestBed{
				Items: []model.TestBedItem{{ResourceHandle: resource.ResourceHandle{Type: resource.ResourceTypeBRNode}, Spec: json.RawMessage("{}")}},
			}},
		},
		{
			name:       "CreateTestbed empty TestBed.Items",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds", handleFunc: h.CreateTestbed,
			dataStruct: request.CreateTestbedRequest{TestBed: model.TestBed{
				Name:         "ut",
				GenerateName: "ut",
			}},
		},
		{
			name:       "CreateTestbed invalid TestBed.Items[].Type",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds", handleFunc: h.CreateTestbed,
			dataStruct: request.CreateTestbedRequest{TestBed: model.TestBed{
				Name:         "ut",
				GenerateName: "ut",
				Items:        []model.TestBedItem{{ResourceHandle: resource.ResourceHandle{Type: "invalid"}, Spec: json.RawMessage("{}")}},
			}},
		},
		{
			name:       "CreateTestbed empty TestBed.Qos[].PodGroup",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds", handleFunc: h.CreateTestbed,
			dataStruct: request.CreateTestbedRequest{TestBed: model.TestBed{
				Name:         "ut",
				GenerateName: "ut",
				Items:        []model.TestBedItem{{ResourceHandle: resource.ResourceHandle{Type: resource.ResourceTypeBRNode}, Spec: json.RawMessage("{}")}},
				Qos:          []resource.QosClaim{{}},
			}},
		},
		{
			name:       "CreateTestbed invalid TestBed.ReclaimPolicy.Lease",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds", handleFunc: h.CreateTestbed,
			dataStruct: request.CreateTestbedRequest{TestBed: model.TestBed{
				Name:          "ut",
				GenerateName:  "ut",
				Items:         []model.TestBedItem{{ResourceHandle: resource.ResourceHandle{Type: resource.ResourceTypeBRNode}, Spec: json.RawMessage("{}")}},
				ReclaimPolicy: resource.ReclaimPolicy{Lease: "invalid"},
			}},
		},
		{
			name:       "CreateTestbed empty TestBed.ReclaimPolicy.GcStrategy.OnCompletion.ReserveEnv.DurationOnWorkingHour",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds", handleFunc: h.CreateTestbed,
			dataStruct: request.CreateTestbedRequest{TestBed: model.TestBed{
				Name:         "ut",
				GenerateName: "ut",
				Items:        []model.TestBedItem{{ResourceHandle: resource.ResourceHandle{Type: resource.ResourceTypeBRNode}, Spec: json.RawMessage("{}")}},
				ReclaimPolicy: resource.ReclaimPolicy{
					GcStrategy: resource.GcStrategy{
						OnCompletion: &resource.GcAction{
							ReserveEnv: &resource.GcReserveEnvAction{DurationOnWorkingHour: ""},
						},
					},
				},
			}},
		},
		{
			name:       "CreateTestbed invalid TestBed.ReclaimPolicy.GcStrategy.OnCompletion.ReserveEnv.DurationOnWorkingHour",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds", handleFunc: h.CreateTestbed,
			dataStruct: request.CreateTestbedRequest{TestBed: model.TestBed{
				Name:         "ut",
				GenerateName: "ut",
				Items:        []model.TestBedItem{{ResourceHandle: resource.ResourceHandle{Type: resource.ResourceTypeBRNode}, Spec: json.RawMessage("{}")}},
				ReclaimPolicy: resource.ReclaimPolicy{
					GcStrategy: resource.GcStrategy{
						OnCompletion: &resource.GcAction{
							ReserveEnv: &resource.GcReserveEnvAction{DurationOnWorkingHour: "invalid"},
						},
					},
				},
			}},
		},
		{
			name:       "GetShootKubeConfig empty Request",
			httpMethod: echo.GET, apiPath: "/api/v1/testbeds/:name/shoot-kubeconfig", handleFunc: h.GetShootKubeConfig,
			pathParams: map[string]string{"name": "ut"},
		},
		{
			name:       "GetTestbed empty Name",
			httpMethod: echo.GET, apiPath: "/api/v1/testbeds/:name", handleFunc: h.GetTestbed,
			pathParams: map[string]string{"name": ""},
		},
		{
			name:       "DeleteTestbed empty Name",
			httpMethod: echo.DELETE, apiPath: "/api/v1/testbeds/:name", handleFunc: h.DeleteTestbed,
			pathParams: map[string]string{"name": ""},
		},
		{
			name:       "RestoreMonitor empty Request",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds/:name/restore-monitor", handleFunc: h.RestoreMonitor,
			dataStruct: request.RestoreMonitorRequest{},
			pathParams: map[string]string{"name": "ut"},
		},
		{
			name:       "RestoreMonitor invalid ClusterType",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds/:name/restore-monitor", handleFunc: h.RestoreMonitor,
			dataStruct: request.RestoreMonitorRequest{Testbed: "ut", ClusterType: "invalid"},
			pathParams: map[string]string{"name": "ut"},
		},
		{
			name:       "AuthTestbed empty Name",
			httpMethod: echo.POST, apiPath: "/api/v1/testbeds/:name/auth", handleFunc: h.AuthTestbed,
			dataStruct: request.AuthTestbedRequest{},
			pathParams: map[string]string{"name": ""},
		},
		{
			name:       "CompleteTestbed empty Name",
			httpMethod: echo.PUT, apiPath: "/api/v1/testbeds/:name/complete?type=Failure", handleFunc: h.CompleteTestbed,
			pathParams: map[string]string{"name": ""},
		},
		{
			name:       "CompleteTestbed invalid Type",
			httpMethod: echo.PUT, apiPath: "/api/v1/testbeds/:name/complete", handleFunc: h.CompleteTestbed,
			pathParams:  map[string]string{"name": "ut"},
			queryString: "?type=invalid",
		},
		{
			name:       "GenerateCredential empty TestbedName",
			httpMethod: echo.POST, apiPath: "/api/v1/credential", handleFunc: h.GenerateCredential,
			dataStruct: request.CredentialRequest{PEM: []byte("ut")},
		},
		{
			name:       "GenerateCredential empty PEM",
			httpMethod: echo.POST, apiPath: "/api/v1/credential", handleFunc: h.GenerateCredential,
			dataStruct: request.CredentialRequest{TestbedName: "ut"},
		},
		{
			name:       "GetResourcePoolStatus empty ResourcePool",
			httpMethod: echo.GET, apiPath: "/api/v1/resource-pool/status", handleFunc: h.GetResourcePoolStatus,
			dataStruct: request.GetResourcePoolStatusRequest{},
		},
		{
			name:       "GetTestbedsResource empty ResourcePool",
			httpMethod: echo.POST, apiPath: "/api/v1/resource-pool/testbeds-resource", handleFunc: h.GetTestbedsResource,
			dataStruct: request.GetTestbedsResourceRequest{},
		},
	}

	for _, testcase := range cases {
		t.Run(testcase.name, func(t *testing.T) {
			data, err := json.Marshal(testcase.dataStruct)
			require.NoError(t, err)

			req := httptest.NewRequest(testcase.httpMethod, testcase.apiPath+testcase.queryString, bytes.NewReader(data))
			req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
			rec := httptest.NewRecorder()
			ctx := utils.NewContext(e.NewContext(req, rec))
			ctx.SetPath(testcase.apiPath)
			for k, v := range testcase.pathParams {
				ctx.SetParamNames(k)
				ctx.SetParamValues(v)
			}
			utils.SetUser(ctx, &utils.User{Name: "unittest"})

			assert.Error(t, testcase.handleFunc(ctx))
		})
	}
}
