package model

import (
	"fmt"
	"testing"
	"time"

	"github.com/onsi/gomega"

	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/db"
)

func TestGetHealthReportByID(t *testing.T) {
	g := gomega.NewWithT(t)
	type GetOption struct {
		UUID       string
		ReportUUID string
		expect     *db.HealthReport
	}
	testGet := func(gOption GetOption) {
		err := InsertOrUpdateReport(&db.HealthReport{FileUUID: gOption.UUID, ReportUUID: gOption.ReportUUID, Status: "running"})
		if err != nil {
			t.Error("insert report failed", err)
			return
		}
		got, err := GetHealthReportByID(gOption.UUID)
		if err != nil {
			t.Error("get health report failed", err)
			return
		}
		g.Expect(got.ID).To(gomega.Equal(uint(1)))
		g.Expect(got.FileUUID).To(gomega.Equal(gOption.UUID))
		g.Expect(got.ReportUUID).To(gomega.Equal(gOption.ReportUUID))
		if err := DeleteHealthReportByID(gOption.UUID); err != nil {
			t.Error("delete package failed", err)
		}
	}
	testGet(GetOption{
		UUID:       "testUUID",
		ReportUUID: "testReportUUID",
		expect: &db.HealthReport{
			FileUUID:   "testUUID",
			ReportUUID: "testReportUUID",
		},
	})
}

func TestInsertOrUpdateReport(t *testing.T) {
	type InsertOption struct {
		UUID             string
		ReportUUID       string
		Status           string
		ChangeStatus     string
		ChangeReportUUID string
	}
	testInsert := func(iOption InsertOption) {
		if err := InsertOrUpdateReport(&db.HealthReport{FileUUID: iOption.UUID, ReportUUID: iOption.ReportUUID, Status: iOption.Status}); err != nil {
			t.Error("insert report failed", err)
			return
		}
		if err := InsertOrUpdateReport(&db.HealthReport{FileUUID: iOption.UUID, Status: iOption.ChangeStatus}); err != nil {
			t.Error("insert report failed", err)
			return
		}
		reGot, err := GetHealthReportByID(iOption.UUID)
		if err != nil {
			t.Error("get health report failed", err)
			return
		}
		if reGot.Status != iOption.ChangeStatus {
			t.Error("got wrong status", reGot, iOption.ChangeStatus)
			return
		}
		if err := DeleteHealthReportByID(iOption.UUID); err != nil {
			t.Error("delete package failed", err)
		}
	}
	testInsert(InsertOption{
		UUID:         "testUUID",
		ReportUUID:   "testReportUUID",
		Status:       "running",
		ChangeStatus: "success",
	})
}

func TestFetchPendingHealthReportJob(t *testing.T) {
	err := InsertOrUpdateReport(&db.HealthReport{FileUUID: "test", Status: string(db.StatusPending)})
	if err != nil {
		t.Error("insert report failed", err)
		return
	}
	err = InsertOrUpdateReport(&db.HealthReport{FileUUID: "test2", Status: string(db.StatusSuccess)})
	if err != nil {
		t.Error("insert report failed", err)
		return
	}
	hrs := make([]*db.HealthReport, 0)
	err = FetchPendingOrRunningHealthReportJob(&hrs)
	if err != nil {
		t.Error("fetch pending report job failed", err)
		return
	}
	if len(hrs) != 1 && hrs[0].FileUUID != "test" {
		t.Error("fetch wrong result", hrs)
	}
}

func TestGetReportCntPerDayByCluster(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	err := InsertUploadFile(&db.UploadFile{
		FileUUID:  "test",
		ClusterID: "foo",
	})
	g.Expect(err).To(gomega.Succeed())
	err = InsertUploadFile(&db.UploadFile{
		FileUUID:  "test2",
		ClusterID: "foo",
	})
	g.Expect(err).To(gomega.Succeed())
	err = InsertUploadFile(&db.UploadFile{
		FileUUID:  "test3",
		ClusterID: "foo",
	})
	g.Expect(err).To(gomega.Succeed())
	err = InsertOrUpdateReport(&db.HealthReport{FileUUID: "test", Status: string(db.StatusPending)})
	g.Expect(err).To(gomega.Succeed())
	err = InsertOrUpdateReport(&db.HealthReport{FileUUID: "test2", Status: string(db.StatusSuccess)})
	g.Expect(err).To(gomega.Succeed())
	err = InsertOrUpdateReport(&db.HealthReport{FileUUID: "test3", Status: string(db.StatusDeleted)})
	g.Expect(err).To(gomega.Succeed())
	_, err = InsertOrUpdateComparisonReport(&db.ComparisonReport{
		ClusterID: "foo",
		OrgID:     42,
		OrgType:   "cloud",
		Status:    string(db.StatusSuccess),
		Creator:   "test",
		Hash:      "xxx",
	})
	g.Expect(err).To(gomega.Succeed())
	_, err = InsertOrUpdateComparisonReport(&db.ComparisonReport{
		ClusterID: "foo",
		OrgID:     42,
		OrgType:   "cloud",
		Status:    string(db.StatusDeleted),
		Creator:   "test",
		Hash:      "yyy",
	})
	g.Expect(err).To(gomega.Succeed())
	// we should get 2 if we exclude an existed health report
	cnt, err := GetReportCntPerDayByClusterExcludeHealthReport("foo", "test")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cnt).To(gomega.Equal(uint64(2)))
	// we should get 3 if we exclude an deleted health report
	cnt, err = GetReportCntPerDayByClusterExcludeHealthReport("foo", "test3")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cnt).To(gomega.Equal(uint64(3)))
	// we should get 3 if we exclude a non-existed health report
	cnt, err = GetReportCntPerDayByClusterExcludeHealthReport("foo", "test4")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cnt).To(gomega.Equal(uint64(3)))

	// we should get 2 if we exclude an existed cmp report
	cnt, err = GetReportCntPerDayByClusterExcludeCmpReport("foo", "xxx")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cnt).To(gomega.Equal(uint64(2)))
	// we should get 3 if we exclude a deleted cmp report
	cnt, err = GetReportCntPerDayByClusterExcludeCmpReport("foo", "yyy")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cnt).To(gomega.Equal(uint64(3)))
	// we should get 3 if we exclude a non-existed cmp report
	cnt, err = GetReportCntPerDayByClusterExcludeCmpReport("foo", "zzz")
	g.Expect(err).To(gomega.Succeed())
	g.Expect(cnt).To(gomega.Equal(uint64(3)))
}

func TestGetComparisonReportByID(t *testing.T) {
	loc, _ := time.LoadLocation("Asia/Shanghai")
	beginTime1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-11 15:07:51", loc)
	endTime1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-11 17:07:51", loc)

	beginTime2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-12 15:07:51", loc)
	endTime2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-12 17:07:51", loc)

	args := &db.ComparisonArg{
		StartTime1: uint(beginTime1.Unix()),
		EndTime1:   uint(endTime1.Unix()),
		PackageID1: []string{"testpackage1_1", "testpackage1_2"},

		StartTime2: uint(beginTime2.Unix()),
		EndTime2:   uint(endTime2.Unix()),
		PackageID2: []string{"testpackage2_1", "testpackage2_2"},
	}
	hash1 := "sdfasdfsdf"

	id, err := InsertOrUpdateComparisonReport(&db.ComparisonReport{
		ClusterID: "123",
		OrgID:     1234,
		OrgType:   "op",
		Hash:      hash1,

		ComparisonArg: args,
		Creator:       "toto@sina.com",
		Status:        "pending",
	})
	if err != nil {
		t.Error("Insert failed", err)
		return
	}
	_, err = GetComparisonReportByID(id)
	if err != nil {
		t.Error(err)
	}
}

func TestInsertOrUpdateComparisonReport(t *testing.T) {
	loc, _ := time.LoadLocation("Asia/Shanghai")
	beginTime1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-11 15:07:51", loc)
	endTime1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-11 17:07:51", loc)

	beginTime2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-12 15:07:51", loc)
	endTime2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-12 17:07:51", loc)

	args := &db.ComparisonArg{
		StartTime1: uint(beginTime1.Unix()),
		EndTime1:   uint(endTime1.Unix()),
		PackageID1: []string{"testpackage1_1", "testpackage1_2"},

		StartTime2: uint(beginTime2.Unix()),
		EndTime2:   uint(endTime2.Unix()),
		PackageID2: []string{"testpackage2_1", "testpackage2_2"},
	}
	hash1 := "sdfasdfsdf"

	id1, err := InsertOrUpdateComparisonReport(&db.ComparisonReport{
		ClusterID: "123",
		OrgID:     1234,
		OrgType:   "op",
		Hash:      hash1,

		ComparisonArg: args,
		Creator:       "toto@sina.com",
		Status:        "pending",
	})
	if err != nil {
		t.Error("Insert failed", err)
		return
	}
	args.PackageID1 = []string{"testpackage1_1", "testpackage1_2", "testpackage1_3"}
	hash2 := "sdfasdfsdf"
	id2, err := InsertOrUpdateComparisonReport(&db.ComparisonReport{
		ClusterID: "123",
		OrgID:     1234,
		OrgType:   "op",
		Hash:      hash2,

		ComparisonArg: args,
		Creator:       "toto@sina.com",
		Status:        "pending",
	})
	if err != nil {
		t.Error(err)
		return
	}
	if id1 != id2 {
		t.Error("insert or update failed", id1, id2)
	}
}

func TestFetchPendingOrRunningComparisonJob(t *testing.T) {
}

func TestLandingComparisonTask(t *testing.T) {
	uf1 := &db.UploadFile{
		FileUUID:         "Test_Join1",
		FileOriginalName: "Test_Join1",
		Alias:            "Test_Join1",
		StorageType:      string(config.AWSStorageType),
		UploadID:         "Test_Join1",
		ClusterID:        "123",
		BeginTime:        formatTime(time.Now()),
		EndTime:          formatTime(time.Now()),
		OrgID:            1111,
		FileLen:          4096,
	}

	uf2 := &db.UploadFile{
		FileUUID:         "Test_Join2",
		FileOriginalName: "Test_Join2",
		Alias:            "Test_Join2",
		StorageType:      string(config.AWSStorageType),
		UploadID:         "Test_Join2",
		ClusterID:        "123",
		BeginTime:        formatTime(time.Now()),
		EndTime:          formatTime(time.Now()),
		OrgID:            1111,
		FileLen:          4096,
	}

	uf3 := &db.UploadFile{
		FileUUID:         "Test_Join3",
		FileOriginalName: "Test_Join3",
		Alias:            "Test_Join3",
		StorageType:      string(config.AWSStorageType),
		UploadID:         "Test_Join3",
		ClusterID:        "123",
		BeginTime:        formatTime(time.Now()),
		EndTime:          formatTime(time.Now()),
		OrgID:            1111,
		FileLen:          4096,
	}
	_ = InsertUploadFile(uf1)
	_ = InsertUploadFile(uf2)
	_ = InsertUploadFile(uf3)

	loc, _ := time.LoadLocation("Asia/Shanghai")
	beginTime1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-11 15:07:51", loc)
	endTime1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-11 17:07:51", loc)

	beginTime2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-12 15:07:51", loc)
	endTime2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-07-12 17:07:51", loc)

	args := &db.ComparisonArg{
		StartTime1: uint(beginTime1.Unix()),
		EndTime1:   uint(endTime1.Unix()),
		PackageID1: []string{"testpackage1_1", "testpackage1_2"},

		StartTime2: uint(beginTime2.Unix()),
		EndTime2:   uint(endTime2.Unix()),
		PackageID2: []string{"testpackage2_1", "testpackage2_2"},
	}
	hash1 := "sdfasdfsdf"

	cr := &db.ComparisonReport{
		ClusterID: "123",
		OrgID:     1234,
		OrgType:   "op",
		Hash:      hash1,

		ComparisonArg: args,
		Creator:       "toto@sina.com",
		Status:        "pending",
	}
	_, err := InsertOrUpdateComparisonReport(cr)
	if err != nil {
		t.Error("Insert failed", err)
		return
	}
	id, err := LandingComparisonTask([]*db.UploadFile{uf1, uf2, uf3}, cr, map[string]struct{}{"rebuildMetric": {}, "rebuildLog": {}, "rebuildConfiguration": {}})
	if err != nil {
		t.Error(err)
		return
	}
	creport, err := GetComparisonReportByID(id)
	if err != nil {
		t.Error(err)
		return
	}
	uf1, err = QueryUploadFile(uf1.FileUUID)
	if err != nil {
		t.Error(err)
		return
	}
	if uf1.RebuildMetricStatus != string(db.StatusPending) || creport.Status != string(db.StatusPending) {
		t.Error("wrong status", creport, uf1)
	}
}

func TestQueryComparisonReport(t *testing.T) {
	for i := 0; i < 20; i++ {
		args := &db.ComparisonArg{
			StartTime1: 123,
			EndTime1:   123,
			PackageID1: []string{"testpackage1_1", "testpackage1_2"},

			StartTime2: 123,
			EndTime2:   123,
			PackageID2: []string{"testpackage2_1", "testpackage2_2"},
		}

		cr := &db.ComparisonReport{
			ClusterID: "123",
			OrgID:     1234,
			OrgType:   "op",
			Hash:      fmt.Sprintf("hash_%d", i),

			ComparisonArg: args,
			Creator:       "toto@sina.com",
			Status:        "pending",
		}
		_, err := InsertOrUpdateComparisonReport(cr)
		if err != nil {
			t.Error(err)
		}
	}
	crs, total, err := QueryComparisonReport(1234, "123", 1, 5)
	if err != nil {
		t.Error(err)
		return
	}
	if total != 21 || len(crs) != 5 || crs[0].CreatedAt.Unix() > crs[1].CreatedAt.Unix() {
		t.Error(total, crs)
		return
	}
}
