package cloudconfig

import (
	"fmt"
	"strings"

	maintent "github.com/tidbcloud/devops/entity/maintenance"
)

type (
	ChangeScopeType               string
	ConfigurationChangePhase      string
	ConfigurationChangeTargetType string
)

// Enums
// Update value list `XXXValues` when update a enum
const (
	ConfigurationChangePhaseCreated      ConfigurationChangePhase = "created"
	ConfigurationChangePhaseApproving    ConfigurationChangePhase = "approving"
	ConfigurationChangePhaseWaitForApply ConfigurationChangePhase = "wait_for_apply"
	ConfigurationChangePhaseApplying     ConfigurationChangePhase = "applying"
	ConfigurationChangePhaseApplied      ConfigurationChangePhase = "applied"
	ConfigurationChangePhaseFailed       ConfigurationChangePhase = "failed"
	ConfigurationChangePhaseCancelled    ConfigurationChangePhase = "cancelled"
	ConfigurationChangePhaseWithdrawn    ConfigurationChangePhase = "withdrawn"

	ConfigurationChangeTargetTypeCluster    ConfigurationChangeTargetType = "cluster"
	ConfigurationChangeTargetTypeTenantInit ConfigurationChangeTargetType = "tenant_init"
	ConfigurationChangeTargetTypeTemplate   ConfigurationChangeTargetType = "template"

	ChangeScopeVersion           ChangeScopeType = "version"
	ChangeScopeParam             ChangeScopeType = "parameters"
	ChangeScopeTemplate          ChangeScopeType = "template"
	ChangeScopeImageTag          ChangeScopeType = "image_tag"
	ChangeScopeOverwriteImageTag ChangeScopeType = "overwirte_image_tag"
)

var (
	ConfigurationChangePhaseValues = []ConfigurationChangePhase{
		ConfigurationChangePhaseCreated, ConfigurationChangePhaseApproving,
		ConfigurationChangePhaseWaitForApply, ConfigurationChangePhaseApplying, ConfigurationChangePhaseApplied,
		ConfigurationChangePhaseFailed,
		ConfigurationChangePhaseCancelled, ConfigurationChangePhaseWithdrawn,
	}
	ConfigurationChangePhaseFinishedValues = []ConfigurationChangePhase{
		ConfigurationChangePhaseApplied, ConfigurationChangePhaseFailed, ConfigurationChangePhaseCancelled,
		ConfigurationChangePhaseWithdrawn,
	}

	ConfigurationChangeTargetTypeValues = []ConfigurationChangeTargetType{
		ConfigurationChangeTargetTypeCluster, ConfigurationChangeTargetTypeTenantInit, ConfigurationChangeTargetTypeTemplate,
	}
)

type ChangedParameter struct {
	Name       string          `json:"name"`
	From       any             `json:"from"`
	To         any             `json:"to"`
	FromSource ParameterSource `json:"from_source"`
	ToSource   ParameterSource `json:"to_source"`
	RiskLevel  RiskLevel       `json:"risk_level"` // "low" or "high"
}

type ChangedTemplate struct {
	From uint64 `json:"from"`
	To   uint64 `json:"to"`
}

type ChangedImageTag struct {
	From string `json:"from"`
	To   string `json:"to"`
}

type ChangedVersion struct {
	From string `json:"from"`
	To   string `json:"to"`
}

type ConfigurationFileChange struct {
	// ID is the id in the database.
	ID uint64 `json:"id"`
	// Filename is the name of the file.
	Filename string `json:"filename"`
	// ConfigType is the config type of the file
	ConfigType string `json:"config_type"`
	// TemplateID is the id of used template.
	// NOTE: If template is changed, it will be the changed template.
	TemplateID uint64 `json:"template_id"`
	// AllParameters contain all used parameters.
	AllParameters []UsedParameter `json:"all_parameters"`
	// ModifiedParameters is the modified configs.
	ModifiedParameters []ChangedParameter `json:"modified_parameters"`
	// ModifiedTemplate is the modified template
	ModifiedTemplate *ChangedTemplate `json:"modified_template"`
	// PreviousText is the file text before the change.
	// It is used to check this change is sequential.
	PreviousText string `json:"previous_text"`
	// ClearCustomParams indicates whether to clear all custom params.
	ClearCustomParams bool `json:"clear_custom_params"`
} // @name cloudconfig.ConfigurationFileChange

func (c *ConfigurationFileChange) ChangedScope() *ChangedScope {
	scope := NewChangedScope()
	if len(c.ModifiedParameters) != 0 {
		scope.Add(ChangeScopeParam, c.Filename)
	}
	if c.ModifiedTemplate != nil {
		scope.Add(ChangeScopeTemplate, c.Filename)
	}
	return scope
}

type ConfigurationVersionChange struct {
	// ID is the id in the database.
	ID   uint64 `json:"id"`
	From string `json:"from"`
	To   string `json:"to"`
} // @name cloudconfig.ConfigurationVersionChange

func (c *ConfigurationVersionChange) ChangedScope() *ChangedScope {
	scope := NewChangedScope()
	scope.Add(ChangeScopeVersion, "")
	return scope
}

type ConfigurationComponentChange struct {
	// ID is the id in the database.
	ID uint64 `json:"id"`
	// Component is the component name.
	Component string `json:"component"`
	// ModifiedVersion records the component version change.
	ModifiedVersion *ChangedVersion `json:"modified_version"`
	// ModifiedImageTag records the change for effective image tag
	ModifiedImageTag *ChangedImageTag `json:"modified_image_tag"`
	// ModifiedOverwriteImageTag contains the the change for overwrite image tag
	ModifiedOverwriteImageTag *ChangedImageTag `json:"modified_overwirte_image_tag"`
} // @name cloudconfig.ConfigurationComponentChange

func (c *ConfigurationComponentChange) ChangedScope() *ChangedScope {
	scope := NewChangedScope()
	if c.ModifiedVersion != nil {
		scope.Add(ChangeScopeVersion, c.Component)
	}
	if c.ModifiedOverwriteImageTag != nil {
		scope.Add(ChangeScopeOverwriteImageTag, c.Component)
	}
	if c.ModifiedImageTag != nil {
		scope.Add(ChangeScopeImageTag, c.Component)
	}
	return scope
}

type ConfigurationChangeScope struct {
	// Files contains changes for configuration files.
	// NOTE: not contain the unchanged files.
	Files []*ConfigurationFileChange `json:"files"`
	// Components contains changes for components
	// NOTE: not contain the unchanged components.
	Components []*ConfigurationComponentChange `json:"components"`
	// Version means the change for cluster version
	Version *ConfigurationVersionChange `json:"version"`
} // @name cloudconfig.ConfigurationChangeScope

type ConfigurationChange struct {
	ID         uint64                        `json:"id"`
	TargetType ConfigurationChangeTargetType `json:"target_type"`
	// TargetVersion is the version of the target.
	// NOTE: If version is changed, it will be the changed version.
	TargetVersion  string                   `json:"target_version"`
	ChangeType     string                   `json:"change_type"`
	Reason         string                   `json:"reason"`
	Applicant      string                   `json:"applicant"`
	OperatorEntity string                   `json:"operator_entity"`
	ApplyPolicy    ParameterApplyPolicy     `json:"apply_policy"`
	Phase          ConfigurationChangePhase `json:"phase"`
	ApprovalID     *uint64                  `json:"approval_id"`
	CreatedAt      int64                    `json:"created_at"`
	ClosedAt       *int64                   `json:"closed_at"`
	AppliedAt      *int64                   `json:"applied_at"`

	ExecutionWindowID *uint64                      `json:"execution_window_id"`
	ExecutionWindow   *maintent.OpsExecutionWindow `json:"execution_window"` // only set in get/list API

	PremiumUpgradeStepID *uint64 `json:"premium_upgrade_step_id,omitempty"`

	Scope ConfigurationChangeScope `json:"scope"`

	TargetCluster    *ClusterConfigurationLocator    `json:"target_cluster"`
	TargetTenantInit *TenantInitConfigurationLocator `json:"target_tenant"`
	TargetTemplate   *ConfigurationTemplateLocator   `json:"target_template"`
} // @name cloudconfig.ConfigurationChange

func (c *ConfigurationChange) ChangedScope() *ChangedScope {
	scope := NewChangedScope()
	for _, fileChange := range c.Scope.Files {
		scope.Merge(fileChange.ChangedScope())
	}
	for _, compChange := range c.Scope.Components {
		scope.Merge(compChange.ChangedScope())
	}
	if c.Scope.Version != nil {
		scope.Merge(c.Scope.Version.ChangedScope())
	}
	return scope
}

func (c *ConfigurationChange) ClusterType() *ClusterType {
	if c.TargetCluster != nil {
		return &c.TargetCluster.ClusterType
	}
	if c.TargetTenantInit != nil {
		return &c.TargetTenantInit.ClusterType
	}
	if c.TargetTemplate != nil {
		return &c.TargetTemplate.ClusterType
	}
	return nil
}

type ChangedScope struct {
	Scopes map[ChangeScopeType][]string `json:"scopes"`
}

func NewChangedScope() *ChangedScope {
	return &ChangedScope{
		Scopes: make(map[ChangeScopeType][]string),
	}
}

// HumanReadable returns a string to print it.
// Format like: "scope:target1&target2, scope2:target3"
func (s *ChangedScope) HumanReadable() string {
	list := make([]string, 0, len(s.Scopes))
	for scope, targets := range s.Scopes {
		list = append(list, fmt.Sprintf("%s:%s", scope, strings.Join(targets, "&")))
	}
	return strings.Join(list, ", ")
}
func (s *ChangedScope) Empty() bool {
	return len(s.Scopes) == 0
}

func (s *ChangedScope) Count() int {
	var count int
	for _, v := range s.Scopes {
		count += len(v)
	}
	return count
}

func (s *ChangedScope) Add(scope ChangeScopeType, target ...string) {
	s.Scopes[scope] = append(s.Scopes[scope], target...)
}
func (s *ChangedScope) Merge(m *ChangedScope) {
	for k, v := range m.Scopes {
		if _, ok := s.Scopes[k]; !ok {
			s.Scopes[k] = v
		} else {
			s.Scopes[k] = append(s.Scopes[k], v...)
		}
	}
}

func (s *ChangedScope) TurnOver() map[string][]*ChangeScopeType {
	m := make(map[string][]*ChangeScopeType)
	for k, v := range s.Scopes {
		for _, target := range v {
			m[target] = append(m[target], &k)
		}
	}
	return m
}
