package handler

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

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"

	"github.com/pingcap/test-infra/common/model"
	reqM "github.com/pingcap/test-infra/common/model/tcms/request"
	respM "github.com/pingcap/test-infra/common/model/tcms/response"
	"github.com/pingcap/test-infra/services/tcms/utils"
)

func newResourcePool() model.ResourcePool {
	return model.ResourcePool{
		Name:    "rp_name3",
		RmsName: "prod",
		Aliases: "default,god",
		BorrowedPolicy: []model.ResourcePoolBorrowedPolicy{{
			TimeStart: "01:00",
			TimeEnd:   "16:00",
			Capacity:  0.3,
		}, {
			TimeStart: "16:00",
			TimeEnd:   "20:00",
			Capacity:  0.6,
		}},
	}
}

func updateResourcePoolsStatus() reqM.ResourcePoolUpdateRequest {
	return reqM.ResourcePoolUpdateRequest{
		ResourcePools: []*model.ResourcePool{
			{
				Name:    "rp_name1",
				RmsName: "prod",
				TotalResource: model.PhysicalResource{
					CPU: model.CPUResource{
						Core: 11,
					},
					Memory: model.MemoryResource{
						Byte: 22,
					},
				},
			},
			{
				Name:    "rp_name2",
				RmsName: "prod",
				NonTestbedOccupiedResource: model.PhysicalResource{
					CPU: model.CPUResource{
						Core: 22,
					},
					Memory: model.MemoryResource{
						Byte: 11,
					},
				},
			},
		},
	}
}

func updateResourcePool() model.ResourcePool {
	return model.ResourcePool{
		Name:    "rp_name2",
		RmsName: "prod",
		Aliases: "default,god",
		BorrowedPolicy: []model.ResourcePoolBorrowedPolicy{{
			TimeStart: "01:00",
			TimeEnd:   "16:00",
			Capacity:  0.3,
		}},
	}
}

func TestListResourcePools(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	req := httptest.NewRequest(echo.GET, "/api/v1/resourcepool?rms_name=prod", nil)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, h.ListResourcePools(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		rps := respM.ResourcePoolListResponse{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rps))
		assert.Equal(t, 4, rps.Total)
		assert.Equal(t, 1.0, rps.Data[0].TotalResource.CPU.Core)
		assert.Equal(t, 2, rps.Data[0].TotalResource.Memory.Byte)
		for _, p := range rps.Data {
			if p.Name == "default" {
				assert.Len(t, p.BorrowedPolicy, 2)
			}
		}
	}
}

func TestCreateResourcePool(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	creatorID := model.ID(3)
	rp := newResourcePool()

	req := httptest.NewRequest(echo.POST, "/api/v1/resourcepool", bytes.NewReader(utils.ToJSON(rp)))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, WithAuth(ctx, h.CreateResourcePool, creatorID))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		p := model.ResourcePool{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &p))
		assert.Equal(t, rp.Name, p.Name)
		assert.Equal(t, rp.TotalResource.CPU.Core, p.TotalResource.CPU.Core)
		assert.Equal(t, rp.BorrowedPolicy, p.BorrowedPolicy)
	}

	req = httptest.NewRequest(echo.GET, "/api/v1/resourcepool?rms_name=prod&names=rp_name3", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, h.ListResourcePools(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		rps := respM.ResourcePoolListResponse{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rps))
		assert.Equal(t, 1, rps.Total)
		assert.Equal(t, "default,god", rps.Data[0].Aliases)
	}

	req = httptest.NewRequest(echo.POST, "/api/v1/resourcepool", bytes.NewReader(utils.ToJSON(rp)))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	err := WithAuth(ctx, h.CreateResourcePool, creatorID)
	if assert.Error(t, err) {
		assert.Equal(t, "Error 400: Bad Request: resource_pool is already exist, name= "+newResourcePool().Name, err.Error())
	}
}

func TestUpdateResourcePoolsStatus(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	creatorID := model.ID(3)
	rps := utils.ToJSON(updateResourcePoolsStatus())

	req := httptest.NewRequest(echo.PUT, "/api/v1/resourcepool", bytes.NewReader(rps))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, WithAuth(ctx, h.UpdateResourcePoolsStatus, creatorID))
	assert.Equal(t, http.StatusOK, rec.Code)

	req = httptest.NewRequest(echo.GET, "/api/v1/resourcepool?rms_name=prod", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, h.ListResourcePools(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		rps := respM.ResourcePoolListResponse{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rps))
		assert.Equal(t, 4, rps.Total)
		assert.Equal(t, 11.0, rps.Data[2].TotalResource.CPU.Core)
		assert.Equal(t, 22, rps.Data[2].TotalResource.Memory.Byte)
		assert.Equal(t, 0.0, rps.Data[3].TotalResource.CPU.Core)
		assert.Equal(t, 0, rps.Data[3].TotalResource.Memory.Byte)
		assert.Equal(t, 22.0, rps.Data[3].NonTestbedOccupiedResource.CPU.Core)
		assert.Equal(t, 11, rps.Data[3].NonTestbedOccupiedResource.Memory.Byte)
	}
}

func TestUpdateResourcePool(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	creatorID := model.ID(3)
	rp := updateResourcePool()

	req := httptest.NewRequest(echo.PUT, "/api/v1/resourcepool", bytes.NewReader(utils.ToJSON(rp)))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, WithAuth(ctx, h.UpdateResourcePool, creatorID))
	assert.Equal(t, http.StatusOK, rec.Code)

	req = httptest.NewRequest(echo.GET, "/api/v1/resourcepool?rms_name=prod&names=rp_name2", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, h.ListResourcePools(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		rps := respM.ResourcePoolListResponse{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rps))
		assert.Equal(t, 1, rps.Total)
		assert.Equal(t, rp.Aliases, rps.Data[0].Aliases)
		assert.Equal(t, rp.BorrowedPolicy, rps.Data[0].BorrowedPolicy)
	}
}

func TestDeleteResourcePools(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	creatorID := model.ID(3)

	req := httptest.NewRequest(echo.DELETE, "/api/v1/resourcepool?rms_name=prod&names=rp_name1,rp_name2,rp_name3", nil)
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, WithAuth(ctx, h.DeleteResourcePools, creatorID))
	assert.Equal(t, http.StatusOK, rec.Code)

	req = httptest.NewRequest(echo.GET, "/api/v1/resourcepool?rms_name=prod", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, h.ListResourcePools(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		rps := respM.ResourcePoolListResponse{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rps))
		assert.Equal(t, 2, rps.Total)
	}
}

func TestCreateRMSs(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	req := httptest.NewRequest(echo.POST, "/api/v1/rms", bytes.NewReader(
		utils.ToJSON(
			model.RMS{
				Name:     "test",
				Endpoint: "http://rms.pingcap.net:30007",
				Token:    "fake token",
			})))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, WithAuth(ctx, h.CreateRMS, model.ID(0)))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		var rms = model.RMS{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rms))
		assert.Equal(t, "test", rms.Name)
	}
}

func TestUpdateRMSs(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	req := httptest.NewRequest(echo.PUT, "/api/v1/rms", bytes.NewReader(
		utils.ToJSON(
			model.RMS{
				Name:  "prod",
				Token: "another fake token",
			})))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, WithAuth(ctx, h.UpdateRMS, model.ID(0)))
	assert.Equal(t, http.StatusOK, rec.Code)

	req = httptest.NewRequest(echo.GET, "/api/v1/rms/:name", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/rms/:name")
	ctx.SetParamNames("name")
	ctx.SetParamValues("prod")
	assert.NoError(t, WithAuth(ctx, h.GetRMS, model.ID(0)))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		var rms = model.RMS{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rms))
		assert.Equal(t, "prod", rms.Name)
		assert.Equal(t, "another fake token", rms.Token)
	}
}

func TestGetRMSs(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	req := httptest.NewRequest(echo.GET, "/api/v1/rms/:name", nil)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/rms/:name")
	ctx.SetParamNames("name")
	ctx.SetParamValues("prod")
	assert.NoError(t, WithAuth(ctx, h.GetRMS, model.ID(0)))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		var rms = model.RMS{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rms))
		assert.Equal(t, "prod", rms.Name)
		assert.Equal(t, "fake token", rms.Token)
	}
}

func TestListRMSs(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	req := httptest.NewRequest(echo.GET, "/api/v1/rms", nil)
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	assert.NoError(t, h.ListRMSs(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		var RMSs []model.RMS
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &RMSs))
		assert.Equal(t, 2, len(RMSs))
		assert.Equal(t, "prod", RMSs[0].Name)
		assert.Equal(t, "", RMSs[0].Token)
	}
}
