package release

import (
	"strings"
	"time"

	coreuser "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/core-server/user/v1"
	appent "github.com/tidbcloud/devops/entity/application"
	"github.com/tidbcloud/devops/pkg/infra/clients/eecli"
)

type (
	ChangeTier         string // @name release.ChangeTier
	ChangeStatus       string // @name release.ChangeStatus
	ChangeResult       string // @name release.ChangeResult
	ChangeType         string // @name release.ChangeType
	RiskLevel          string // @name release.RiskLevel
	RegionName         string // @name release.RegionName
	ChangeDeployMethod string // @name release.ChangeDeployMethod
	ChangeDeployStatus string // @name release.ChangeDeployStatus
	ChangeDeployOrder  int    // @name release.ChangeDeployOrder
)

func (r RegionName) IsAWS() bool {
	return strings.HasPrefix(string(r), "aws")
}

func (r RegionName) IsGCP() bool {
	return strings.HasPrefix(string(r), "gcp")
}

func (o ChangeDeployOrder) IsFirst() bool {
	return o == 0
}

func (o ChangeDeployOrder) Int() int {
	return int(o)
}

// Enums
// Update value list `XXXValues` when update a enum
const (
	ChangeTierDedicated  ChangeTier = "dedicated"
	ChangeTierServerless ChangeTier = "serverless"
	ChangeTierPremium    ChangeTier = "premium"

	ChangeStatusDraft         ChangeStatus = "draft"
	ChangeStatusCreated       ChangeStatus = "created"
	ChangeStatusApproving     ChangeStatus = "approving"
	ChangeStatusWaitForDeploy ChangeStatus = "wait_for_deploy"
	ChangeStatusInProgress    ChangeStatus = "in_progress"
	ChangeStatusObserving     ChangeStatus = "observing"
	ChangeStatusClosed        ChangeStatus = "closed"
	ChangeStatusWithdrawn     ChangeStatus = "withdrawn"

	ChangeResultCancel          ChangeResult = "cancel"
	ChangeResultSuccessful      ChangeResult = "successful"
	ChangeResultFailed          ChangeResult = "failed"
	ChangeResultFailedPartially ChangeResult = "failed_partially"
	ChangeResultRollback        ChangeResult = "rollback"

	ChangeTypeSprint ChangeType = "sprint"
	ChangeTypeHotfix ChangeType = "hotfix"

	RiskLevelLow  RiskLevel = "low"
	RiskLevelHigh RiskLevel = "high"
	// RiskLevelMedium RiskLevel = "medium" // useless now

	// only define regions that we used
	RegionNameGCPUsWest1 RegionName = "us-west1"
	RegionNameAWSUsWest2 RegionName = "us-west-2"

	ChangeDeployMethodArgoCD ChangeDeployMethod = "argocd"
	ChangeDeployMethodManual ChangeDeployMethod = "manual"
	ChangeDeployMethodEEAPI  ChangeDeployMethod = "ee_api"

	ChangeDeployStatusCreated  ChangeDeployStatus = "created"
	ChangeDeployStatusDeployed ChangeDeployStatus = "deployed"
	ChangeDeployStatusVerified ChangeDeployStatus = "verified"
)

var (
	ChangeTierValues   = []ChangeTier{ChangeTierDedicated, ChangeTierServerless, ChangeTierPremium}
	ChangeStatusValues = []ChangeStatus{
		ChangeStatusDraft, ChangeStatusCreated, ChangeStatusApproving,
		ChangeStatusWaitForDeploy, ChangeStatusInProgress, ChangeStatusObserving,
		ChangeStatusClosed, ChangeStatusWithdrawn,
	}
	ChangeResultValues = []ChangeResult{
		ChangeResultCancel, ChangeResultSuccessful,
		ChangeResultFailed, ChangeResultFailedPartially, ChangeResultRollback,
	}
	ChangeTypeValues = []ChangeType{
		ChangeTypeSprint, ChangeTypeHotfix,
	}
	RiskLevelValues          = []RiskLevel{RiskLevelLow, RiskLevelHigh}
	ChangeDeployMethodValues = []ChangeDeployMethod{ChangeDeployMethodArgoCD, ChangeDeployMethodManual, ChangeDeployMethodEEAPI}
	ChangeDeployStatusValues = []ChangeDeployStatus{
		ChangeDeployStatusCreated, ChangeDeployStatusDeployed, ChangeDeployStatusVerified,
	}
)

// ChangeInRepo is the change that stored in repo
type ChangeInRepo struct {
	ID uint64 `json:"id"`

	Name            string        `json:"name"`
	WindowStartAt   int64         `json:"window_start_at"` // timestamp, zero means unlimited
	WindowEndAt     int64         `json:"window_end_at"`   // timestamp, zero means unlimited
	CreatedAt       time.Time     `json:"created_at"`      // readonly
	ClosedAt        *time.Time    `json:"closed_at"`
	Tier            []ChangeTier  `json:"tier"`
	RiskLevel       RiskLevel     `json:"risk_level"`
	ChangeItemNames []string      `json:"change_item_names"` // release ticket names
	Status          ChangeStatus  `json:"status"`
	StatusReason    string        `json:"status_reason"` // the reason to describe why the status changed or pending
	Result          *ChangeResult `json:"result"`
	Type            ChangeType    `json:"type"`

	Detail       *ChangeDetail       `json:"detail"`
	Deployments  []*ChangeDeployment `json:"deployments"`
	Notification *ChangeNotification `json:"notification"`

	ReleaseID       uint64   `json:"release_id"` // release id
	ApplicantEmail  string   `json:"applicant_email"`
	ApplicationName string   `json:"application_id"`
	ApprovalID      *uint64  `json:"approval_id"`       // the used approval id
	PrevApprovalIDs []uint64 `json:"prev_approval_ids"` // the previous approval ids
}

func (c *ChangeInRepo) GetDeployment(order ChangeDeployOrder) *ChangeDeployment {
	for _, d := range c.Deployments {
		if d.Order == order {
			return d
		}
	}
	return nil
}

func (c *ChangeInRepo) LastDeployment() *ChangeDeployment {
	var last *ChangeDeployment
	for _, d := range c.Deployments {
		if last == nil || d.Order > last.Order {
			last = d
		}
	}
	return last
}

func (c *ChangeInRepo) WindowStartTime() time.Time {
	return time.Unix(c.WindowStartAt, 0)
}

func (c *ChangeInRepo) WindowEndTime() time.Time {
	return time.Unix(c.WindowEndAt, 0)
}

type Change struct {
	ChangeInRepo `json:",inline"`

	ChangeItems []*ReleaseTicket    `json:"change_items"`
	Release     *Release            `json:"release"`
	Applicant   *coreuser.User      `json:"applicant"`
	Application *appent.Application `json:"application"`
} // @name release.Change

// ChangeDetail contains some fields that only record not used.
type ChangeDetail struct {
	ID uint64 `json:"id"`

	NeedOutage        bool    `json:"need_outage"`
	Note              string  `json:"note"`
	RollbackPlan      string  `json:"rollback_plan"`
	StagingTestReport string  `json:"staging_test_report"`
	Summary           *string `json:"summary"`
} // @name release.ChangeDetail

type ChangeNotification struct {
	ID uint64 `json:"id"`

	LarkTopicCreatedAt *time.Time `json:"lark_topic_created_at"`
	LarkTopicID        *string    `json:"lark_topic_id"`
	NotifiedToCloseAt  *time.Time `json:"notified_to_close_at"`
} // @name release.ChangeNotification

type ChangeDeployment struct {
	ID uint64 `json:"id"`

	Order      ChangeDeployOrder  `json:"order"`
	Regions    []RegionName       `json:"regions"`
	DeployedAt *time.Time         `json:"deployed_at,omitempty"`
	Status     ChangeDeployStatus `json:"status"`
	Method     ChangeDeployMethod `json:"method"`
	Info       ChangeDeployInfo   `json:"info"`
	Note       string             `json:"note"`
} // @name release.ChangeDeployment

type GithubPR struct {
	RepoOwner string `json:"repo_owner"`
	RepoName  string `json:"repo_name"`
	PRNumber  int64  `json:"pr_number"`
	Link      string `json:"link"`
}

type ChangeDeployArgoCD struct {
	DeployPRs []GithubPR `json:"deploy_prs"`
}

type ChangeDeployManual struct {
	// Deprecated, use ChangeDeployInfo.Description instead.
	Description string `json:"description"`
}

type ChangeDeployEEAPI struct {
	EEAPIBuildNgImage *eecli.BumpNgTagRequest `json:"ee_api_build_ng_image"`
}

type ChangeDeployInfo struct {
	// save as JSON in DB, so all fields MUST be set json tag.
	Description string              `json:"description,omitempty"`
	ArgoCD      *ChangeDeployArgoCD `json:"argocd,omitempty"`
	Manual      *ChangeDeployManual `json:"manual,omitempty"`
	EEAPI       *ChangeDeployEEAPI  `json:"ee_api,omitempty"`
}

// TODO: ChangeStat
