package handler

import (
	"fmt"
	"net/http"
	"net/url"
	"testing"
	"time"

	"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"
	htest "github.com/pingcap/test-infra/services/tcms/tests/httptest"
)

const NEW_CASE_NAME = "case100"

func newCase() reqM.CaseCreateRequest {
	return reqM.CaseCreateRequest{
		TestCaseBase: model.TestCaseBase{
			Name: NEW_CASE_NAME,
			Framework: model.FrameworkUnion{
				Generic: &model.GenericFrameworkSpec{
					Command:         []string{"ls -lah"},
					WorkingDir:      "/apps",
					Env:             map[string]string{"LOG_LEVEL": "1"},
					Image:           "ghcr.io/homebrew/core/redis:6.2.5",
					ImagePullPolicy: "Always",
					DefaultTimeout:  0,
				},
			},
			Parameters: []model.CaseParamDecl{{
				Type:    model.CaseParamTypeString,
				Name:    "param1",
				Default: "123",
			}, {
				Type:    model.CaseParamTypeResource,
				Name:    "param2",
				Default: "456",
			}, {
				Type:    model.CaseParamTypeResource,
				Name:    "param_-3",
				Default: "456",
			}},
		},
		TestCaseMeta: model.TestCaseMeta{
			Summary:       "123",
			Preconditions: &[]string{`empty`}[0],
			Steps: &[]string{`1. tiup install cdc server with config file: https://docs.pingcap.com/zh/tidb/stable/tiup-cluster-topology-reference#cdc_servers
	2. tiup destroy cdc server`}[0],
			ExpectedResult: &[]string{`1. sync task successfully
	2. check data don't miss 
	3. data consistent"`}[0],
			Priority:          model.CasePriorityP1,
			Category:          model.CaseCategoryPerformance,
			SupportedVersions: []string{">=1,<=4", "5.0.2"},
			Tags:              []string{"tidb/executor", "bar"},
			Automation:        model.CaseAutomationStatusAutomated,
		},
		Designer: "gaozifeng@pingcap.com",
	}
}

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

	_, cases := htest.MustGET[respM.CaseListResponse](h.Echo, "/api/v1/cases?designer=guanzhongyang@pingcap.com&offset=0&count=2")
	assert.Equal(t, 3, cases.Total)
	assert.Equal(t, 2, len(cases.Data))
}

// FIXME: this test is not working
//
// func TestQueryCases(t *testing.T) {
// 	h := setup()
//  defer h.Shutdown()

// 	rec := htest.Serve(e, echo.GET, "/api/v1/cases?query=case&offset=0&count=2", nil)
// 	rec := httptest.NewRecorder()
// 	c := utils.NewContext(h.Echo.NewContext(req, rec))
// 	assert.NoError(t, h.ListCases(c))
// 	// 		cases := respM.CaseSearchResponse{}
//
// 		assert.Equal(t, 3, cases.Total)
// 		assert.Equal(t, 2, len(cases.Data))
// 	}
// }

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

	_, cases := htest.MustGET[respM.CaseListResponse](h.Echo, "/api/v1/cases/autocomplete?query=cas")
	assert.Equal(t, 3, cases.Total)
}

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

	_, tc := htest.MustGET[model.TestCase](h.Echo, "/api/v1/cases/2")
	assert.Equal(t, 2, int(tc.ID))
	assert.NotNil(t, tc.Framework.Generic)
	assert.Equal(t, 2, len(tc.Tags))
	assert.NotNil(t, tc.UpdatedAt)
}

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

	toTs := url.QueryEscape(time.Now().Format(time.RFC3339Nano))
	_, cs := htest.MustGET[[]model.HistoryRecord](h.Echo, "/api/v1/cases/2/histories?offset=0&count=1&from_ts=1970-01-02T00:00:00Z&to_ts="+toTs)
	c := cs[0]
	assert.NotZero(t, c.Revision)
	assert.False(t, c.CreatedAt.IsZero())
	assert.Equal(t, model.HistoryActionCreate, c.Action)
}

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

	// Export single case.
	_, tc := htest.MustGET[CaseWithAdditionalInfo](h.Echo, "/api/v1/cases/2?tcctl=1")
	assert.NotEmpty(t, tc.Name)
	assert.Equal(t, 2, int(tc.ID))
	assert.NotNil(t, tc.Framework.Generic)
	assert.NotNil(t, tc.UpdatedAt)

	// Export multiple cases.
	_, tcs := htest.MustGET[[]CaseWithAdditionalInfo](h.Echo, "/api/v1/cases?designer=guanzhongyang%40pingcap.com&tags=aaa-bbb&tcctl=1", htest.WithResponseMultiYAML(true))
	// t.Errorf("\n%s\n", string(body))
	for _, tc := range tcs {
		assert.NotEmpty(t, tc.Name)
		assert.NotNil(t, tc.Framework.Generic)
		assert.NotNil(t, tc.UpdatedAt)
	}
}

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

	nc := newCase()
	nc.Name = "a-_.a"

	_, c := htest.MustPOST[model.TestCase](h.Echo, "/api/v1/cases", htest.WithJSON(nc))
	assert.Equal(t, nc.Name, c.Name)
	assert.NotNil(t, c.Framework.Generic)
	assert.Equal(t, len(nc.Parameters), len(c.Parameters))
	assert.Equal(t, nc.SupportedVersions, c.SupportedVersions)
	assert.Equal(t, nc.ExpectedResult, c.ExpectedResult)
	assert.Equal(t, nc.Preconditions, c.Preconditions)
	assert.Equal(t, nc.Steps, c.Steps)
	assert.Equal(t, nc.Priority, c.Priority)
	assert.Equal(t, nc.Designer, c.DesignedBy.Email)
	assert.Equal(t, len(nc.Tags), len(c.Tags))

	// case is already exist, failed
	rec, _ := htest.POST[any](h.Echo, "/api/v1/cases", htest.WithJSON(nc))
	assert.Equal(t, http.StatusBadRequest, rec.Code)

	// get case history
	now := time.Now()
	_, tc := htest.MustGET[model.TestCase](h.Echo, fmt.Sprintf("/api/v1/cases/%d?ts=%s", c.ID, url.QueryEscape(now.Format(time.RFC3339Nano))))
	assert.Equal(t, len(c.Tags), len(c.Tags))
	assert.NotNil(t, tc.UpdatedAt)
	assert.Equal(t, c.Name, tc.Name)
	assert.Equal(t, c.DesignedBy, tc.DesignedBy)

	// get case history that before creation should be nil
	yesterday := now.Add(-24 * time.Hour)
	rec, _ = htest.GET[any](h.Echo, fmt.Sprintf("/api/v1/cases/%d?ts=%s", c.ID, url.QueryEscape(yesterday.Format(time.RFC3339))))
	assert.Equal(t, http.StatusNotFound, rec.Code)
}

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

	expectedResult := "adasdqwdwqdqdqdqwd"
	nc := newCase()
	nc.ExpectedResult = &expectedResult
	nc.Tags = nil

	// update case 1
	_, c := htest.MustPUT[model.TestCase](h.Echo, "/api/v1/cases/1", htest.WithJSON(nc))
	assert.Equal(t, NEW_CASE_NAME, c.Name)
	assert.NotNil(t, c.Framework.Generic)
	assert.Equal(t, len(nc.Parameters), len(c.Parameters))
	assert.Equal(t, expectedResult, *c.ExpectedResult)
	assert.Equal(t, nc.Summary, c.Summary)
	assert.Equal(t, nc.Automation, c.Automation)
	assert.Equal(t, nc.Designer, c.DesignedBy.Email)
	assert.Equal(t, len(nc.Tags), len(c.Tags))

	// check case history
	now := time.Now()
	_, tc := htest.MustGET[model.TestCase](h.Echo, fmt.Sprintf("/api/v1/cases/%d?ts=%s", c.ID, url.QueryEscape(now.Format(time.RFC3339Nano))))
	assert.Equal(t, len(c.Tags), len(c.Tags))
	assert.NotNil(t, tc.UpdatedAt)
	assert.Equal(t, c.Name, tc.Name)
	assert.Equal(t, c.DesignedBy, tc.DesignedBy)
}

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

	// delete case 3
	caseID := model.ID(3)
	_, result := htest.MustDELETE[map[string]string](h.Echo, "/api/v1/cases/"+caseID.String())
	assert.Equal(t, map[string]string{"result": "ok"}, result)

	// get case 3
	_, tc := htest.MustGET[model.TestCase](h.Echo, "/api/v1/cases/"+caseID.String())
	assert.NotNil(t, tc.DeletedAt)

	// get case history
	now := time.Now()
	_, tc = htest.MustGET[model.TestCase](h.Echo, fmt.Sprintf("/api/v1/cases/%d?ts=%s", caseID, url.QueryEscape(now.Format(time.RFC3339Nano))))
	assert.NotNil(t, tc.UpdatedAt)
	assert.Equal(t, caseID, tc.ID)
	assert.NotZero(t, tc.DesignedBy.ID)
	assert.NotEmpty(t, tc.DesignedBy.Email)
}
