package cvt

import (
	"encoding/json"
	"fmt"
	"sort"
	"strconv"
	"strings"

	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/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
)

type e2m struct{}

func (cvt e2m) Change(e *relent.ChangeInRepo) *relmod.Change {
	m := &relmod.Change{
		Updatable: base.Updatable{
			Base: base.Base{ID: base.ID(e.ID)},
		},

		Name:            e.Name,
		WindowStartAt:   e.WindowStartTime(),
		WindowEndAt:     e.WindowEndTime(),
		ClosedAt:        e.ClosedAt,
		Tier:            cvt.ChangeTiers(e.Tier),
		RiskLevel:       relmod.RiskLevel(e.RiskLevel),
		ChangeItemNames: cvt.ChangeItemNames(e.ChangeItemNames),
		Status:          relmod.ChangeStatus(e.Status),
		StatusReason:    e.StatusReason,
		Type:            relmod.ChangeType(e.Type),
	}
	if e.Result != nil {
		m.Result = pointer.Pointer(relmod.ChangeResult(*e.Result))
	}

	if e.Detail != nil {
		m.Detail = cvt.ChangeDetail(e.ID, e.Detail)
	}
	for _, d := range e.Deployments {
		m.Deployments = append(m.Deployments, cvt.ChangeDeployment(e.ID, d))
	}
	if e.Notification != nil {
		m.Notification = cvt.ChangeNotification(e.ID, e.Notification)
	}

	m.ReleaseID = base.ID(e.ReleaseID)
	m.ApplicantEmail = e.ApplicantEmail
	m.ApplicationName = e.ApplicationName
	if e.ApprovalID != nil {
		m.ApprovalID = pointer.Pointer(base.ID(*e.ApprovalID))
	}
	m.PrevApprovalIDs = cvt.PrevApprovalIDs(e.PrevApprovalIDs)

	return m
}

func (cvt e2m) ChangeDetail(changeID uint64, e *relent.ChangeDetail) *relmod.ChangeDetail {
	m := &relmod.ChangeDetail{
		Updatable: base.Updatable{
			Base: base.Base{ID: base.ID(e.ID)},
		},

		ChangeID:          base.ID(changeID),
		NeedOutage:        e.NeedOutage,
		Note:              e.Note,
		RollbackPlan:      e.RollbackPlan,
		StagingTestReport: e.StagingTestReport,
		Summary:           e.Summary,
	}

	return m
}

func (cvt e2m) ChangeNotification(changeID uint64, e *relent.ChangeNotification) *relmod.ChangeNotification {
	m := &relmod.ChangeNotification{
		Updatable: base.Updatable{
			Base: base.Base{ID: base.ID(e.ID)},
		},

		ChangeID:           base.ID(changeID),
		LarkTopicCreatedAt: e.LarkTopicCreatedAt,
		LarkTopicID:        e.LarkTopicID,
		NotifiedToCloseAt:  e.NotifiedToCloseAt,
	}
	return m
}

func (cvt e2m) ChangeDeployment(changeID uint64, e *relent.ChangeDeployment) *relmod.ChangeDeployment {
	infoData, err := json.Marshal(e.Info)
	if err != nil {
		panic(fmt.Errorf("failed to marshal deployment info: %s", err.Error()))
	}

	m := &relmod.ChangeDeployment{
		Updatable: base.Updatable{
			Base: base.Base{ID: base.ID(e.ID)},
		},

		ChangeID: base.ID(changeID),

		Order:      e.Order.Int(),
		Regions:    cvt.Regions(e.Regions),
		DeployedAt: e.DeployedAt,
		Status:     relmod.ChangeDeployStatus(e.Status),
		Method:     relmod.ChangeDeployMethod(e.Method),
		Info:       infoData,
		Note:       e.Note,
	}

	return m
}

func (cvt e2m) Regions(e []relent.RegionName) string {
	s := container.TransformSlice(e, container.ToString[relent.RegionName])
	sort.Strings(s)
	return strings.Join(s, ",")
}

func (cvt e2m) ChangeItemNames(e []string) string {
	sort.Strings(e)
	return strings.Join(e, ",")
}

func (cvt e2m) PrevApprovalIDs(e []uint64) string {
	sort.Slice(e, func(i, j int) bool { return e[i] < e[j] })
	s := container.TransformSlice(e, func(item uint64) string { return strconv.FormatUint(item, 10) })
	return strings.Join(s, ",")
}

func (cvt e2m) ChangeTiers(e []relent.ChangeTier) string {
	s := container.TransformSlice(e, container.ToString[relent.ChangeTier])
	sort.Strings(s)
	return strings.Join(s, ",")
}

func (cvt e2m) Release(e *relent.ReleaseInRepo) *relmod.Release {
	m := &relmod.Release{
		Updatable: base.Updatable{
			Base: base.Base{ID: base.ID(e.ID)},
		},

		Name:          e.Name,
		Type:          relmod.ReleaseType(e.Type),
		WindowStartAt: e.WindowStartAt,
		WindowEndAt:   e.WindowEndAt,
		ClosedAt:      e.ClosedAt,
		Status:        relmod.ReleaseStatus(e.Status),
	}

	return m
}

func (cvt e2m) ReleaseTicket(e *relent.ReleaseTicketInRepo) *relmod.ReleaseTicket {
	m := &relmod.ReleaseTicket{
		Name:          e.Name,
		Summary:       e.Summary,
		Status:        e.Status,
		Type:          relmod.ReleaseTicketType(e.Type),
		AssigneeEmail: e.AssigneeEmail,
		ReleaseID:     base.ID(e.ReleaseID),
		ReleaseName:   e.ReleaseName,
	}
	for _, prLoc := range e.GithubPRLocations {
		m.GithubPRLocations = append(m.GithubPRLocations, relmod.ReleaseTicketToGithubPR{
			Name:       e.Name,
			PRLocation: string(prLoc),
		})
	}

	return m
}
