package repo

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

	opent "github.com/tidbcloud/devops/entity/operation"
	opmod "github.com/tidbcloud/devops/models/operation"
	"github.com/tidbcloud/devops/pkg/infra/utils/test"
	"github.com/tidbcloud/devops/pkg/service/operation/repo/cvt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"gorm.io/gorm"
)

func TestRepo(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Operation Repo Suite", test.ReporterConfig())
}

var _ = Describe("[OperationRepo]", func() {
	var fw = test.Framework()

	Describe("[InstanceDriver]", func() {
		var (
			d InstanceDriver
		)

		BeforeEach(func() {
			fw.InitUTDB()

			d = (*instanceDriver)(&driver{
				cvter:      cvt.NewConverter(),
				dbProvider: fw.UTDBProvider,
			})
		})

		Describe("[Save]", func() {
			It("persists execution window id", func() {
				ctx := context.Background()
				executionWindowID := uint64(20)
				operationName := fmt.Sprintf("execution-window-storage-%d", time.Now().UnixNano())

				saved, err := d.Save(ctx, &opent.Instance{
					Type:           opent.OperationTargetCluster,
					Operation:      operationName,
					Reason:         "reason",
					Applicant:      "applicant",
					OperatorEntity: "user:applicant",
					Params: map[opent.ParamName]string{
						opent.ParamClusterID: operationName,
					},
					Outputs:           map[opent.OutputName]string{},
					ExecutionWindowID: &executionWindowID,
					Phase:             opent.InstancePhaseCreated,
				})
				Expect(err).ShouldNot(HaveOccurred())
				Expect(saved.ExecutionWindowID).ShouldNot(BeNil())
				Expect(*saved.ExecutionWindowID).To(Equal(executionWindowID))
				Expect(saved.OperatorEntity).To(Equal("user:applicant"))

				loaded, err := d.Load(ctx, saved.ID)
				Expect(err).ShouldNot(HaveOccurred())
				Expect(loaded.ExecutionWindowID).ShouldNot(BeNil())
				Expect(*loaded.ExecutionWindowID).To(Equal(executionWindowID))
				Expect(loaded.OperatorEntity).To(Equal("user:applicant"))

				instances, err := d.Query(ctx, InstanceOpts.OperationIn([]string{operationName}))
				Expect(err).ShouldNot(HaveOccurred())
				Expect(instances).Should(HaveLen(1))
				Expect(instances[0].ExecutionWindowID).ShouldNot(BeNil())
				Expect(*instances[0].ExecutionWindowID).To(Equal(executionWindowID))
				Expect(instances[0].OperatorEntity).To(Equal("user:applicant"))
			})

			It("filters created_at by unix timestamp", func() {
				createAfter := int64(1779000000)
				createBefore := int64(1779426353)

				sql := fw.UTDB.ToSQL(func(tx *gorm.DB) *gorm.DB {
					db := tx.Model(&opmod.OperationInstance{})
					db = InstanceOpts.CreateAfter(createAfter).Apply(db)
					db = InstanceOpts.CreateBefore(createBefore).Apply(db)
					return db.Find(&[]opmod.OperationInstance{})
				})

				Expect(sql).To(ContainSubstring("created_at >= FROM_UNIXTIME(1779000000)"))
				Expect(sql).To(ContainSubstring("created_at <= FROM_UNIXTIME(1779426353)"))
			})

			It("filters params by JSON fragments", func() {
				ctx := context.Background()
				operationName := fmt.Sprintf("UpdateNgGlobalComponentImage-%d", time.Now().UnixNano())

				matching, err := d.Save(ctx, &opent.Instance{
					Type:      opent.OperationTargetUndefined,
					Operation: operationName,
					Applicant: "applicant",
					Params: map[opent.ParamName]string{
						opent.ParamCloudConfigClusterType: "SHARED_POOL",
						opent.ParamNgModifyImageComponent: "tikv",
					},
					Outputs: map[opent.OutputName]string{},
					Phase:   opent.InstancePhaseCreated,
				})
				Expect(err).ShouldNot(HaveOccurred())

				_, err = d.Save(ctx, &opent.Instance{
					Type:      opent.OperationTargetUndefined,
					Operation: operationName,
					Applicant: "applicant",
					Params: map[opent.ParamName]string{
						opent.ParamCloudConfigClusterType: "LOGICAL_CLUSTER",
						opent.ParamNgModifyImageComponent: "tikv",
					},
					Outputs: map[opent.OutputName]string{},
					Phase:   opent.InstancePhaseCreated,
				})
				Expect(err).ShouldNot(HaveOccurred())

				_, err = d.Save(ctx, &opent.Instance{
					Type:      opent.OperationTargetUndefined,
					Operation: operationName,
					Applicant: "applicant",
					Params: map[opent.ParamName]string{
						opent.ParamCloudConfigClusterType: "SHARED_POOL",
						opent.ParamNgModifyImageComponent: "tidb",
					},
					Outputs: map[opent.OutputName]string{},
					Phase:   opent.InstancePhaseCreated,
				})
				Expect(err).ShouldNot(HaveOccurred())

				instances, err := d.Query(ctx,
					InstanceOpts.OperationIn([]string{operationName}),
					InstanceOpts.ParamsLike(map[opent.ParamName]string{
						opent.ParamCloudConfigClusterType: "SHARED_POOL",
						opent.ParamNgModifyImageComponent: "tikv",
					}),
				)
				Expect(err).ShouldNot(HaveOccurred())
				Expect(instances).Should(HaveLen(1))
				Expect(instances[0].ID).To(Equal(matching.ID))

				sql := fw.UTDB.ToSQL(func(tx *gorm.DB) *gorm.DB {
					db := tx.Model(&opmod.OperationInstance{})
					db = InstanceOpts.ParamsLike(map[opent.ParamName]string{
						opent.ParamCloudConfigClusterType: "SHARED_POOL",
						opent.ParamNgModifyImageComponent: "tikv",
					}).Apply(db)
					return db.Find(&[]opmod.OperationInstance{})
				})
				Expect(sql).To(ContainSubstring("params LIKE"))
				Expect(sql).To(ContainSubstring(`"cluster_type": "SHARED_POOL"`))
				Expect(sql).To(ContainSubstring(`"component": "tikv"`))
			})
		})
	})
})
