package cvt

import (
	"testing"
	"time"

	gitent "github.com/tidbcloud/devops/entity/github"
	relent "github.com/tidbcloud/devops/entity/release"
	"github.com/tidbcloud/devops/models/base"
	relmod "github.com/tidbcloud/devops/models/release"
	"github.com/tidbcloud/devops/pkg/infra/clients/eecli"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/infra/utils/test"
	"gorm.io/gorm"

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

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

var _ = Describe("[Release Converter]", func() {

	Describe("[Change]", func() {
		Context("[Model To Entity]", func() {
			It("should not miss fields when converting", func() {
				time := time.Now()

				// every fields MUST be set as a non zero value
				org := &relmod.Change{
					Updatable: base.Updatable{
						Base:      base.Base{ID: base.ID(1), CreatedAt: time},
						UpdatedAt: pointer.Pointer(time),
					},
					Name:            "test",
					WindowStartAt:   time,
					WindowEndAt:     time,
					ClosedAt:        pointer.Pointer(time),
					Tier:            string(relent.ChangeTierDedicated),
					RiskLevel:       relmod.RiskLevelLow,
					ChangeItemNames: "DM-50,DM-60",
					Status:          relmod.ChangeStatusCreated,
					StatusReason:    "test status reason",
					Result:          pointer.Pointer(relmod.ChangeResultSuccessful),
					Type:            relmod.ChangeTypeSprint,

					Detail: &relmod.ChangeDetail{
						Updatable: base.Updatable{
							Base:      base.Base{ID: base.ID(10), CreatedAt: time},
							UpdatedAt: pointer.Pointer(time),
						},
						ChangeID:          base.ID(1),
						NeedOutage:        true,
						Note:              "test",
						RollbackPlan:      "test",
						StagingTestReport: "https://report.link",
						Summary:           pointer.Pointer("test"),
					},
					Deployments: []*relmod.ChangeDeployment{
						{
							Updatable: base.Updatable{
								Base:      base.Base{ID: base.ID(30), CreatedAt: time},
								UpdatedAt: pointer.Pointer(time),
							},
							DeletedAt: gorm.DeletedAt{
								Time:  time,
								Valid: true,
							},
							ChangeID:   base.ID(1),
							Order:      1,
							Regions:    "us-west-2,us-west1",
							DeployedAt: pointer.Pointer(time),
							Method:     relmod.ChangeDeployMethodArgoCD,
							Status:     relmod.ChangeDeployStatusCreated,
							Info:       []byte(`{"description":"description","argocd":{"deploy_prs":[{"repo_owner":"tidbcloud","repo_name":"devops_ut","pr_number":10080,"link":"http://pr.link"}]},"manual":{"description":"manual"},"ee_api":{"ee_api_build_ng_image":{"author":"devops-test","prLink":"http://ee-api-pr-link-updated","branch":"main","commit":"commit-sha-updated","repo":"pingcap/tidb","meta":{"ops_req":{"applicant":"devops-test","release_id":"100","change_id":"1"}}}}}`),
							Note:       "deployment note",
						},
					},
					Notification: &relmod.ChangeNotification{
						Updatable: base.Updatable{
							Base:      base.Base{ID: base.ID(40), CreatedAt: time},
							UpdatedAt: pointer.Pointer(time),
						},
						ChangeID:           base.ID(1),
						LarkTopicCreatedAt: pointer.Pointer(time),
						LarkTopicID:        pointer.Pointer("test"),
						NotifiedToCloseAt:  pointer.Pointer(time),
					},

					ReleaseID:       base.ID(40),
					ApplicantEmail:  "test@pingcap.com",
					ApplicationName: "test-application",
					ApprovalID:      pointer.Pointer(base.ID(70)),
					PrevApprovalIDs: "50,60",
				}
				exp := &relent.ChangeInRepo{
					ID:              1,
					Name:            "test",
					WindowStartAt:   time.Unix(),
					WindowEndAt:     time.Unix(),
					CreatedAt:       time,
					ClosedAt:        &time,
					Tier:            []relent.ChangeTier{relent.ChangeTierDedicated},
					RiskLevel:       relent.RiskLevelLow,
					ChangeItemNames: []string{"DM-50", "DM-60"},
					Status:          relent.ChangeStatusCreated,
					StatusReason:    "test status reason",
					Result:          pointer.Pointer(relent.ChangeResultSuccessful),
					Type:            relent.ChangeTypeSprint,

					Detail: &relent.ChangeDetail{
						ID:                10,
						NeedOutage:        true,
						Note:              "test",
						RollbackPlan:      "test",
						StagingTestReport: "https://report.link",
						Summary:           pointer.Pointer("test"),
					},
					Deployments: []*relent.ChangeDeployment{
						{
							ID:         30,
							Order:      1,
							Regions:    []relent.RegionName{relent.RegionNameAWSUsWest2, relent.RegionNameGCPUsWest1},
							DeployedAt: &time,
							Method:     relent.ChangeDeployMethodArgoCD,
							Status:     relent.ChangeDeployStatusCreated,
							Info: relent.ChangeDeployInfo{
								Description: "description",
								ArgoCD: &relent.ChangeDeployArgoCD{
									DeployPRs: []relent.GithubPR{
										{
											RepoOwner: "tidbcloud",
											RepoName:  "devops_ut",
											PRNumber:  10080,
											Link:      "http://pr.link",
										},
									},
								},
								Manual: &relent.ChangeDeployManual{
									Description: "manual",
								},
								EEAPI: &relent.ChangeDeployEEAPI{
									EEAPIBuildNgImage: &eecli.BumpNgTagRequest{
										Author: "devops-test",
										PRLink: "http://ee-api-pr-link-updated",
										Branch: "main",
										Commit: "commit-sha-updated",
										Repo:   eecli.TiDBRepoTiDB,
										Meta: eecli.BumpNgTagReqMeta{
											OpsReq: eecli.BumpNgTagOpsReq{
												Applicant: "devops-test",
												ReleaseID: "100",
												ChangeID:  "1",
											},
										},
									},
								},
							},
							Note: "deployment note",
						},
					},
					Notification: &relent.ChangeNotification{
						ID:                 40,
						LarkTopicCreatedAt: pointer.Pointer(time),
						LarkTopicID:        pointer.Pointer("test"),
						NotifiedToCloseAt:  pointer.Pointer(time),
					},

					ReleaseID:       40,
					ApplicantEmail:  "test@pingcap.com",
					ApplicationName: "test-application",
					ApprovalID:      pointer.Pointer(uint64(70)),
					PrevApprovalIDs: []uint64{50, 60},
				}

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

				got := m2e{}.Change(org)
				Expect(test.CheckNonZeroValue(got, nil)).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("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 := &relent.ChangeInRepo{
					ID:              1,
					Name:            "test",
					WindowStartAt:   time.Unix(),
					WindowEndAt:     time.Unix(),
					ClosedAt:        &time,
					Tier:            []relent.ChangeTier{relent.ChangeTierDedicated},
					RiskLevel:       relent.RiskLevelLow,
					ChangeItemNames: []string{"DM-50", "DM-60"},
					Status:          relent.ChangeStatusCreated,
					StatusReason:    "test status reason",
					Result:          pointer.Pointer(relent.ChangeResultSuccessful),
					Type:            relent.ChangeTypeSprint,

					Detail: &relent.ChangeDetail{
						ID:                10,
						NeedOutage:        true,
						Note:              "test",
						RollbackPlan:      "test",
						StagingTestReport: "https://report.link",
						Summary:           pointer.Pointer("test"),
					},
					Deployments: []*relent.ChangeDeployment{
						{
							ID:         30,
							Order:      1,
							Regions:    []relent.RegionName{relent.RegionNameAWSUsWest2, relent.RegionNameGCPUsWest1},
							DeployedAt: &time,
							Method:     relent.ChangeDeployMethodArgoCD,
							Status:     relent.ChangeDeployStatusCreated,
							Info: relent.ChangeDeployInfo{
								Description: "description",
								ArgoCD: &relent.ChangeDeployArgoCD{
									DeployPRs: []relent.GithubPR{
										{
											RepoOwner: "tidbcloud",
											RepoName:  "devops_ut",
											PRNumber:  10080,
											Link:      "http://pr.link",
										},
									},
								},
								Manual: &relent.ChangeDeployManual{
									Description: "manual",
								},
								EEAPI: &relent.ChangeDeployEEAPI{
									EEAPIBuildNgImage: &eecli.BumpNgTagRequest{
										Author: "devops-test",
										PRLink: "http://ee-api-pr-link-updated",
										Branch: "main",
										Commit: "commit-sha-updated",
										Repo:   eecli.TiDBRepoTiDB,
										Meta: eecli.BumpNgTagReqMeta{
											OpsReq: eecli.BumpNgTagOpsReq{
												Applicant: "devops-test",
												ReleaseID: "100",
												ChangeID:  "1",
											},
										},
									},
								},
							},
							Note: "deployment note",
						},
					},
					Notification: &relent.ChangeNotification{
						ID:                 40,
						LarkTopicCreatedAt: &time,
						LarkTopicID:        pointer.Pointer("test"),
						NotifiedToCloseAt:  pointer.Pointer(time),
					},

					ReleaseID:       40,
					ApplicantEmail:  "test@pingcap.com",
					ApplicationName: "test-application",
					ApprovalID:      pointer.Pointer(uint64(70)),
					PrevApprovalIDs: []uint64{50, 60},
				}
				exp := &relmod.Change{
					Updatable: base.Updatable{
						Base: base.Base{ID: base.ID(1)},
					},
					Name:            "test",
					WindowStartAt:   time,
					WindowEndAt:     time,
					ClosedAt:        pointer.Pointer(time),
					Tier:            string(relent.ChangeTierDedicated),
					RiskLevel:       relmod.RiskLevelLow,
					ChangeItemNames: "DM-50,DM-60",
					Status:          relmod.ChangeStatusCreated,
					StatusReason:    "test status reason",
					Result:          pointer.Pointer(relmod.ChangeResultSuccessful),
					Type:            relmod.ChangeTypeSprint,

					Detail: &relmod.ChangeDetail{
						Updatable: base.Updatable{
							Base: base.Base{ID: base.ID(10)},
						},
						ChangeID:          base.ID(1),
						NeedOutage:        true,
						Note:              "test",
						RollbackPlan:      "test",
						StagingTestReport: "https://report.link",
						Summary:           pointer.Pointer("test"),
					},
					Deployments: []*relmod.ChangeDeployment{
						{
							Updatable: base.Updatable{
								Base: base.Base{ID: base.ID(30)},
							},
							ChangeID:   base.ID(1),
							Order:      1,
							Regions:    "us-west-2,us-west1",
							DeployedAt: pointer.Pointer(time),
							Method:     relmod.ChangeDeployMethodArgoCD,
							Status:     relmod.ChangeDeployStatusCreated,
							Info:       []byte(`{"description":"description","argocd":{"deploy_prs":[{"repo_owner":"tidbcloud","repo_name":"devops_ut","pr_number":10080,"link":"http://pr.link"}]},"manual":{"description":"manual"},"ee_api":{"ee_api_build_ng_image":{"author":"devops-test","prLink":"http://ee-api-pr-link-updated","branch":"main","commit":"commit-sha-updated","repo":"pingcap/tidb","meta":{"ops_req":{"applicant":"devops-test","release_id":"100","change_id":"1"}}}}}`),
							Note:       "deployment note",
						},
					},
					Notification: &relmod.ChangeNotification{
						Updatable: base.Updatable{
							Base: base.Base{ID: base.ID(40)},
						},
						ChangeID:           base.ID(1),
						LarkTopicCreatedAt: &time,
						LarkTopicID:        pointer.Pointer("test"),
						NotifiedToCloseAt:  pointer.Pointer(time),
					},

					ReleaseID:       base.ID(40),
					ApplicantEmail:  "test@pingcap.com",
					ApplicationName: "test-application",
					ApprovalID:      pointer.Pointer(base.ID(70)),
					PrevApprovalIDs: "50,60",
				}
				ignoredFields := []string{
					".Updatable.Base.CreatedAt",
					".Updatable.UpdatedAt",
					".Detail.Base.CreatedAt",
					".Detail.Updatable.UpdatedAt",
					".Deployments[0].Base.CreatedAt",
					".Deployments[0].Updatable.UpdatedAt",
					".Deployments[0].DeletedAt.Valid",
					".Notification.Base.CreatedAt",
					".Notification.Updatable.UpdatedAt",
				}

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

				got := e2m{}.Change(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())
			})
		})
	})

	Describe("[Release]", func() {
		Context("[Model To Entity]", func() {
			It("should not miss fields when converting", func() {
				time := time.Now()

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

					Name:          "test",
					Type:          relmod.ReleaseTypePeriodic,
					WindowStartAt: time,
					WindowEndAt:   time,
					ClosedAt:      &time,
					Status:        relmod.ReleaseStatusBeforeRelease,
				}
				exp := &relent.ReleaseInRepo{
					ID:            1,
					Name:          "test",
					Type:          relent.ReleaseTypePeriodic,
					WindowStartAt: time,
					WindowEndAt:   time,
					ClosedAt:      &time,
					Status:        relent.ReleaseStatusBeforeRelease,
				}

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

				got := m2e{}.Release(org)
				Expect(test.CheckNonZeroValue(got, nil)).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("should not miss fields when converting", func() {
				time := time.Now()

				// every fields MUST be set as a non zero value
				org := &relent.ReleaseInRepo{
					ID:            1,
					Name:          "test",
					Type:          relent.ReleaseTypePeriodic,
					WindowStartAt: time,
					WindowEndAt:   time,
					ClosedAt:      &time,
					Status:        relent.ReleaseStatusBeforeRelease,
				}
				exp := &relmod.Release{
					Updatable: base.Updatable{
						Base: base.Base{ID: base.ID(1)},
					},
					Name:          "test",
					Type:          relmod.ReleaseTypePeriodic,
					WindowStartAt: time,
					WindowEndAt:   time,
					ClosedAt:      &time,
					Status:        relmod.ReleaseStatusBeforeRelease,
				}
				ignoredFields := []string{
					".Updatable.Base.CreatedAt",
					".Updatable.UpdatedAt",
				}

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

				got := e2m{}.Release(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())
			})
		})
	})

	Describe("[ReleaseTickets]", func() {
		Context("[Model To Entity]", func() {
			It("should not miss fields when converting", func() {
				time := time.Now()
				releaseName := "release-20230905"

				// every fields MUST be set as a non zero value
				org := &relmod.ReleaseTicket{
					Name:      "DM-8580",
					CreatedAt: time,
					UpdatedAt: &time,

					Summary:       "test",
					Status:        "READY FOR RELEASE",
					Type:          relmod.ReleaseTicketTypeDM,
					AssigneeEmail: "test",
					ReleaseID:     base.ID(1),
					ReleaseName:   &releaseName,
					GithubPRLocations: []relmod.ReleaseTicketToGithubPR{
						{
							Name:       "DM-8580",
							PRLocation: "tidbcloud/devops#1",
						},
						{
							Name:       "DM-8580",
							PRLocation: "tidbcloud/devops#2",
						},
					},
				}
				exp := &relent.ReleaseTicketInRepo{
					Name:          "DM-8580",
					Summary:       "test",
					Status:        relent.ReleaseTicketStatusReady,
					Type:          relent.ReleaseTicketTypeDM,
					AssigneeEmail: "test",
					ReleaseID:     1,
					ReleaseName:   &releaseName,
					GithubPRLocations: []gitent.PRLocation{
						"tidbcloud/devops#1",
						"tidbcloud/devops#2",
					},
				}

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

				got := m2e{}.ReleaseTicket(org)
				Expect(test.CheckNonZeroValue(got, nil)).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("should not miss fields when converting", func() {
				releaseName := "release-20230905"

				// every fields MUST be set as a non zero value
				org := &relent.ReleaseTicketInRepo{
					Name:          "DM-8580",
					Summary:       "test",
					Status:        relent.ReleaseTicketStatusReady,
					Type:          relent.ReleaseTicketTypeDM,
					AssigneeEmail: "test",
					ReleaseID:     1,
					ReleaseName:   &releaseName,
					GithubPRLocations: []gitent.PRLocation{
						"tidbcloud/devops#1",
						"tidbcloud/devops#2",
					},
				}
				exp := &relmod.ReleaseTicket{
					Name: "DM-8580",

					Summary:       "test",
					Status:        "READY FOR RELEASE",
					Type:          relmod.ReleaseTicketTypeDM,
					AssigneeEmail: "test",
					ReleaseID:     base.ID(1),
					ReleaseName:   &releaseName,
					GithubPRLocations: []relmod.ReleaseTicketToGithubPR{
						{
							Name:       "DM-8580",
							PRLocation: "tidbcloud/devops#1",
						},
						{
							Name:       "DM-8580",
							PRLocation: "tidbcloud/devops#2",
						},
					},
				}
				ignoredFields := []string{
					".CreatedAt",
					".UpdatedAt",
					".DeletedAt",
				}

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

				got := e2m{}.ReleaseTicket(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())
			})
		})
	})

})
