package cvt

import (
	"testing"
	"time"

	maintent "github.com/tidbcloud/devops/entity/maintenance"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/models/base"
	opmod "github.com/tidbcloud/devops/models/operation"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/infra/utils/test"

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

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

var _ = Describe("[Operation Converter]", func() {
	Describe("Instance", func() {
		Context("[Model To Entity]", func() {
			It("should not miss fields when converting", func() {
				time := *test.ParseTimeOrDie(time.DateTime, "2023-06-12 11:00:00")

				// every fields MUST be set as a non zero value
				org := &opmod.OperationInstance{
					Updatable: base.Updatable{
						Base:      base.Base{ID: base.ID(1), CreatedAt: time},
						UpdatedAt: &time,
					},

					Type:              string(opent.OperationTargetCluster),
					Operation:         "operation",
					Reason:            "reason",
					Params:            []byte(`{"param-1": "value-1"}`),
					Outputs:           []byte(`{"output-1": "value-1"}`),
					Applicant:         "applicant",
					OperatorEntity:    "user:applicant",
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					Phase:             string(opent.InstancePhaseCreated),
					ApprovalID:        pointer.Pointer(uint64(1)),
					ClosedAt:          &time,

					Actions: []*opmod.OperationInstanceAction{
						{
							Updatable: base.Updatable{
								Base:      base.Base{ID: base.ID(1), CreatedAt: time},
								UpdatedAt: &time,
							},
							Order:           1,
							Name:            "action-1",
							Type:            string(opent.ActionTypeAuto),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusRunning),
							Output:          "output-1",
							InstanceID:      1,
						},
						{
							Updatable: base.Updatable{
								Base:      base.Base{ID: base.ID(2), CreatedAt: time},
								UpdatedAt: &time,
							},
							Order:           2,
							Name:            "action-2",
							Type:            string(opent.ActionTypeManual),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusCreated),
							Output:          "output-2",
							InstanceID:      1,
						},
					},

					ToTarget: &opmod.OperationInstanceToTargetMapping{
						InstanceID: 1,
						TargetType: string(opent.OperationTargetCluster),
						TargetID:   "1",
					},
				}
				exp := &opent.Instance{
					ID:             1,
					Type:           opent.OperationTargetCluster,
					Operation:      "operation",
					Reason:         "reason",
					Applicant:      "applicant",
					OperatorEntity: "user:applicant",
					Params: map[opent.ParamName]string{
						"param-1": "value-1",
					},
					Outputs: map[opent.OutputName]string{
						"output-1": "value-1",
					},
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					Phase:             opent.InstancePhaseCreated,
					ApprovalID:        pointer.Pointer(uint64(1)),
					CreatedAt:         time.Unix(),
					ClosedAt:          pointer.Pointer(time.Unix()),
					Actions: []*opent.InstanceAction{
						{
							ID:              1,
							Order:           1,
							Name:            "action-1",
							Type:            opent.ActionTypeAuto,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusRunning,
							Output:          "output-1",
						},
						{
							ID:              2,
							Order:           2,
							Name:            "action-2",
							Type:            opent.ActionTypeManual,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusCreated,
							Output:          "output-2",
						},
					},
				}

				Expect(test.CheckNonZeroValue(org, nil)).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")

				got := m2e{}.Instance(org)
				Expect(test.CheckNonZeroValue(got, []string{".TargetDetail", ".ExecutionWindow"})).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")
				Expect(test.IsEqual(exp, got)).To(BeNil())
			})

		})

		Context("[Entity To Model]", func() {
			It("[Target Cluster] should not miss fields when converting", func() {
				time := *test.ParseTimeOrDie(time.DateTime, "2023-06-12 11:00:00")

				// every fields MUST be set as a non zero value
				org := &opent.Instance{
					ID:             1,
					Type:           opent.OperationTargetCluster,
					Operation:      "operation",
					Reason:         "reason",
					Applicant:      "applicant",
					OperatorEntity: "user:applicant",
					Params: map[opent.ParamName]string{
						opent.ParamClusterID: "123",
						opent.ParamTenantID:  "456",
					},
					Outputs: map[opent.OutputName]string{
						"output-1": "value-1",
					},
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					ExecutionWindow:   &maintent.OpsExecutionWindow{ID: 20, CreatedAt: 1, UpdatedAt: 2, StartAt: 100, EndAt: 200},
					Phase:             opent.InstancePhaseCreated,
					ApprovalID:        pointer.Pointer(uint64(1)),
					CreatedAt:         time.Unix(),
					ClosedAt:          pointer.Pointer(time.Unix()),
					Actions: []*opent.InstanceAction{
						{
							ID:              1,
							Order:           1,
							Name:            "action-1",
							Type:            opent.ActionTypeAuto,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusRunning,
							Output:          "output-1",
						},
						{
							ID:              2,
							Order:           2,
							Name:            "action-2",
							Type:            opent.ActionTypeManual,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusCreated,
							Output:          "output-2",
						},
					},
				}
				exp := &opmod.OperationInstance{
					Updatable: base.Updatable{
						Base: base.Base{ID: base.ID(1)},
					},

					Type:              string(opent.OperationTargetCluster),
					Operation:         "operation",
					Reason:            "reason",
					Params:            []byte(`{"cluster_id":"123","tenant_id":"456"}`),
					Outputs:           []byte(`{"output-1":"value-1"}`),
					Applicant:         "applicant",
					OperatorEntity:    "user:applicant",
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					Phase:             string(opent.InstancePhaseCreated),
					ApprovalID:        pointer.Pointer(uint64(1)),
					ClosedAt:          &time,

					Actions: []*opmod.OperationInstanceAction{
						{
							Updatable: base.Updatable{
								Base: base.Base{ID: base.ID(1)},
							},
							Order:           1,
							Name:            "action-1",
							Type:            string(opent.ActionTypeAuto),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusRunning),
							Output:          "output-1",
							InstanceID:      1,
						},
						{
							Updatable: base.Updatable{
								Base: base.Base{ID: base.ID(2)},
							},
							Order:           2,
							Name:            "action-2",
							Type:            string(opent.ActionTypeManual),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusCreated),
							Output:          "output-2",
							InstanceID:      1,
						},
					},

					ToTarget: &opmod.OperationInstanceToTargetMapping{
						InstanceID: 1,
						TargetType: string(opent.OperationTargetCluster),
						TargetID:   "123",
					},
				}
				ignoredFields := []string{
					".Updatable.Base.CreatedAt",
					".Updatable.UpdatedAt",
					".Actions[0].Updatable.Base.CreatedAt",
					".Actions[0].Updatable.UpdatedAt",
					".Actions[1].Updatable.Base.CreatedAt",
					".Actions[1].Updatable.UpdatedAt",
				}

				Expect(test.CheckNonZeroValue(org, []string{".TargetDetail"})).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")

				got := e2m{}.Instance(org)
				Expect(test.CheckNonZeroValue(got, ignoredFields)).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")
				Expect(test.IsEqual(exp, got)).To(BeNil())
			})

			It("[Target Tenant] should not miss fields when converting", func() {
				time := *test.ParseTimeOrDie(time.DateTime, "2023-06-12 11:00:00")

				// every fields MUST be set as a non zero value
				org := &opent.Instance{
					ID:             1,
					Type:           opent.OperationTargetTenant,
					Operation:      "operation",
					Reason:         "reason",
					Applicant:      "applicant",
					OperatorEntity: "user:applicant",
					Params: map[opent.ParamName]string{
						opent.ParamClusterID: "123",
						opent.ParamTenantID:  "456",
					},
					Outputs: map[opent.OutputName]string{
						"output-1": "value-1",
					},
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					ExecutionWindow:   &maintent.OpsExecutionWindow{ID: 20, CreatedAt: 1, UpdatedAt: 2, StartAt: 100, EndAt: 200},
					Phase:             opent.InstancePhaseCreated,
					ApprovalID:        pointer.Pointer(uint64(1)),
					CreatedAt:         time.Unix(),
					ClosedAt:          pointer.Pointer(time.Unix()),
					Actions: []*opent.InstanceAction{
						{
							ID:              1,
							Order:           1,
							Name:            "action-1",
							Type:            opent.ActionTypeAuto,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusRunning,
							Output:          "output-1",
						},
						{
							ID:              2,
							Order:           2,
							Name:            "action-2",
							Type:            opent.ActionTypeManual,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusCreated,
							Output:          "output-2",
						},
					},
				}
				exp := &opmod.OperationInstance{
					Updatable: base.Updatable{
						Base: base.Base{ID: base.ID(1)},
					},

					Type:              string(opent.OperationTargetTenant),
					Operation:         "operation",
					Reason:            "reason",
					Params:            []byte(`{"cluster_id":"123","tenant_id":"456"}`),
					Outputs:           []byte(`{"output-1":"value-1"}`),
					Applicant:         "applicant",
					OperatorEntity:    "user:applicant",
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					Phase:             string(opent.InstancePhaseCreated),
					ApprovalID:        pointer.Pointer(uint64(1)),
					ClosedAt:          &time,

					Actions: []*opmod.OperationInstanceAction{
						{
							Updatable: base.Updatable{
								Base: base.Base{ID: base.ID(1)},
							},
							Order:           1,
							Name:            "action-1",
							Type:            string(opent.ActionTypeAuto),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusRunning),
							Output:          "output-1",
							InstanceID:      1,
						},
						{
							Updatable: base.Updatable{
								Base: base.Base{ID: base.ID(2)},
							},
							Order:           2,
							Name:            "action-2",
							Type:            string(opent.ActionTypeManual),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusCreated),
							Output:          "output-2",
							InstanceID:      1,
						},
					},

					ToTarget: &opmod.OperationInstanceToTargetMapping{
						InstanceID: 1,
						TargetType: string(opent.OperationTargetTenant),
						TargetID:   "456",
					},
				}
				ignoredFields := []string{
					".Updatable.Base.CreatedAt",
					".Updatable.UpdatedAt",
					".Actions[0].Updatable.Base.CreatedAt",
					".Actions[0].Updatable.UpdatedAt",
					".Actions[1].Updatable.Base.CreatedAt",
					".Actions[1].Updatable.UpdatedAt",
				}

				Expect(test.CheckNonZeroValue(org, []string{".TargetDetail"})).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")

				got := e2m{}.Instance(org)
				Expect(test.CheckNonZeroValue(got, ignoredFields)).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")
				Expect(test.IsEqual(exp, got)).To(BeNil())
			})

			It("[Target Project] should not miss fields when converting", func() {
				time := *test.ParseTimeOrDie(time.DateTime, "2023-06-12 11:00:00")

				// every fields MUST be set as a non zero value
				org := &opent.Instance{
					ID:             1,
					Type:           opent.OperationTargetProject,
					Operation:      "operation",
					Reason:         "reason",
					Applicant:      "applicant",
					OperatorEntity: "user:applicant",
					Params: map[opent.ParamName]string{
						opent.ParamProjectID: "789",
					},
					Outputs: map[opent.OutputName]string{
						"output-1": "value-1",
					},
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					ExecutionWindow:   &maintent.OpsExecutionWindow{ID: 20, CreatedAt: 1, UpdatedAt: 2, StartAt: 100, EndAt: 200},
					Phase:             opent.InstancePhaseCreated,
					ApprovalID:        pointer.Pointer(uint64(1)),
					CreatedAt:         time.Unix(),
					ClosedAt:          pointer.Pointer(time.Unix()),
					Actions: []*opent.InstanceAction{
						{
							ID:              1,
							Order:           1,
							Name:            "action-1",
							Type:            opent.ActionTypeAuto,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusRunning,
							Output:          "output-1",
						},
						{
							ID:              2,
							Order:           2,
							Name:            "action-2",
							Type:            opent.ActionTypeManual,
							StartPolicy:     opent.ActionStartPolicySchedule,
							StartScheduleAt: pointer.Pointer(time.Unix()),
							StartedAt:       pointer.Pointer(time.Unix()),
							FinishedAt:      pointer.Pointer(time.Unix()),
							Status:          opent.ActionStatusCreated,
							Output:          "output-2",
						},
					},
				}
				exp := &opmod.OperationInstance{
					Updatable: base.Updatable{
						Base: base.Base{ID: base.ID(1)},
					},

					Type:              string(opent.OperationTargetProject),
					Operation:         "operation",
					Reason:            "reason",
					Params:            []byte(`{"project_id":"789"}`),
					Outputs:           []byte(`{"output-1":"value-1"}`),
					Applicant:         "applicant",
					OperatorEntity:    "user:applicant",
					ExecutionWindowID: pointer.Pointer(uint64(20)),
					Phase:             string(opent.InstancePhaseCreated),
					ApprovalID:        pointer.Pointer(uint64(1)),
					ClosedAt:          &time,

					Actions: []*opmod.OperationInstanceAction{
						{
							Updatable: base.Updatable{
								Base: base.Base{ID: base.ID(1)},
							},
							Order:           1,
							Name:            "action-1",
							Type:            string(opent.ActionTypeAuto),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusRunning),
							Output:          "output-1",
							InstanceID:      1,
						},
						{
							Updatable: base.Updatable{
								Base: base.Base{ID: base.ID(2)},
							},
							Order:           2,
							Name:            "action-2",
							Type:            string(opent.ActionTypeManual),
							StartPolicy:     string(opent.ActionStartPolicySchedule),
							StartScheduleAt: &time,
							StartedAt:       &time,
							FinishedAt:      &time,
							Status:          string(opent.ActionStatusCreated),
							Output:          "output-2",
							InstanceID:      1,
						},
					},

					ToTarget: &opmod.OperationInstanceToTargetMapping{
						InstanceID: 1,
						TargetType: string(opent.OperationTargetProject),
						TargetID:   "789",
					},
				}
				ignoredFields := []string{
					".Updatable.Base.CreatedAt",
					".Updatable.UpdatedAt",
					".Actions[0].Updatable.Base.CreatedAt",
					".Actions[0].Updatable.UpdatedAt",
					".Actions[1].Updatable.Base.CreatedAt",
					".Actions[1].Updatable.UpdatedAt",
				}

				Expect(test.CheckNonZeroValue(org, []string{".TargetDetail"})).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")

				got := e2m{}.Instance(org)
				Expect(test.CheckNonZeroValue(got, ignoredFields)).To(BeNil(), "some fields missed, maybe added new fields but forgot to update converter")
				Expect(test.IsEqual(exp, got)).To(BeNil())
			})
		})
	})

})
