package api

import (
	"context"
	"slices"

	ccproto "github.com/tidbcloud/config-center/proto/config_center"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	cmxsvcapi "github.com/tidbcloud/devops/pkg/service/clustermaintenance/api"
)

type ApplyMethod string

const (
	DataflowApply     ApplyMethod = "dataflow_apply"
	MgmtServiceApply  ApplyMethod = "mgmt_service_apply"
	ConfigCenterApply ApplyMethod = "config_center_apply"
	ServerlessApply   ApplyMethod = "serverless_apply"
)

// ApplyMode specifies the methods that can be applied.
type ApplyMode struct {
	Allow []ApplyMethod `json:"allow"`
	Deny  []ApplyMethod `json:"deny"`
}

func (m *ApplyMode) Appliable(method ApplyMethod) bool {
	if m == nil {
		return true
	}

	if len(m.Allow) != 0 {
		return slices.Contains(m.Allow, method)
	}

	if len(m.Deny) != 0 {
		return !slices.Contains(m.Deny, method)
	}

	return true
}

type ChangeType string

const (
	ChangeTypeDefault             ChangeType = ""     // for old frontend
	ChangeTypeTidb                ChangeType = "tidb" // tidb/tikv/pd/tiflash/backup/restore/ticdc/lightning/data-migration
	ChangeTypeTiproxy             ChangeType = "tiproxy"
	ChangeTypeFullTidb            ChangeType = "fulltidb"
	ChangeTypeDm                  ChangeType = "data-migration"
	ChangeTypeTiCDC               ChangeType = "ticdc"
	ChangeTypeChangefeed          ChangeType = "changefeed"
	ChangeTypeSharedPool          ChangeType = "sharedpool"
	ChangeTypeLogicalCluster      ChangeType = "logicalcluster"
	ChangeTypeTiCDCNg             ChangeType = "ticdc_ng"
	ChangeTypeChangefeedNg        ChangeType = "changefeed_ng"
	ChangeTypeChangefeedEssential ChangeType = "changefeed_essential"
)

// use version from MainComponent as cluster version
func (t ChangeType) MainComponent() string {
	switch t {
	case ChangeTypeTidb, ChangeTypeDefault, ChangeTypeFullTidb:
		return ccproto.DEDICATED_TIDB_COMPONENT_TIDB
	case ChangeTypeTiproxy:
		return ccproto.DEDICATED_TIDB_COMPONENT_TIPROXY
	case ChangeTypeDm:
		return ccproto.DEDICATED_TIDB_COMPONENT_DM
	case ChangeTypeTiCDC, ChangeTypeChangefeed, ChangeTypeChangefeedNg, ChangeTypeChangefeedEssential, ChangeTypeTiCDCNg:
		return ccproto.DEDICATED_TIDB_COMPONENT_TICDC
	case ChangeTypeSharedPool:
		return ccproto.SHARED_POOL_COMPONENT_TIKV
	case ChangeTypeLogicalCluster:
		return ccproto.LOGICAL_CLUSTER_COMPONENT_TIDB
	}
	return ""
}

func (t ChangeType) VersionComponent() string {
	switch t {
	case ChangeTypeTidb, ChangeTypeDefault, ChangeTypeFullTidb:
		return ccproto.DEDICATED_TIDB_COMPONENT_TIDB
	case ChangeTypeTiproxy:
		return ccproto.DEDICATED_TIDB_COMPONENT_TIPROXY
	case ChangeTypeDm:
		return ccproto.DEDICATED_TIDB_COMPONENT_DM
	case ChangeTypeTiCDC, ChangeTypeChangefeed:
		return ccproto.TICDC_COMPONENT_TICDC
	case ChangeTypeSharedPool, ChangeTypeLogicalCluster:
		return ""
	}
	return ""
}

func (t ChangeType) RelatedComponents() []string {
	switch t {
	case ChangeTypeTidb, ChangeTypeDefault:
		return []string{
			ccproto.DEDICATED_TIDB_COMPONENT_TIDB,
			ccproto.DEDICATED_TIDB_COMPONENT_TIKV,
			ccproto.DEDICATED_TIDB_COMPONENT_PD,
			ccproto.DEDICATED_TIDB_COMPONENT_TIFLASH,
			ccproto.DEDICATED_TIDB_COMPONENT_TICDC,
			ccproto.DEDICATED_TIDB_COMPONENT_RESTORE,
			ccproto.DEDICATED_TIDB_COMPONENT_BACKUP,
			ccproto.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_BR,
			ccproto.DEDICATED_TIDB_COMPONENT_COMPACT_BACKUP_TIKV,
			ccproto.DEDICATED_TIDB_COMPONENT_TIDB_LIGHTNING,
			// ccproto.DEDICATED_TIDB_COMPONENT_DM,			DM has its own upgrade page
			// ccproto.DEDICATED_TIDB_COMPONENT_TIPROXY,	Do not update tiproxy when update tidb
		}
	case ChangeTypeTiproxy:
		return []string{
			ccproto.DEDICATED_TIDB_COMPONENT_TIPROXY,
		}
	case ChangeTypeDm:
		return []string{
			ccproto.DEDICATED_TIDB_COMPONENT_DM,
		}
	case ChangeTypeFullTidb:
		return ccproto.ClusterType_DEDICATED_TIDB.AvailableComponents()
	case ChangeTypeTiCDC, ChangeTypeTiCDCNg:
		return []string{
			ccproto.TICDC_COMPONENT_TICDC,
		}
	case ChangeTypeSharedPool:
		return ccproto.ClusterType_SHARED_POOL.AvailableComponents()
	case ChangeTypeLogicalCluster:
		return ccproto.ClusterType_LOGICAL_CLUSTER.AvailableComponents()
	}
	return nil
}

func (t ChangeType) ComponentExist(component string) bool {
	for _, c := range t.RelatedComponents() {
		if c == component {
			return true
		}
	}
	return false
}

func (t ChangeType) RelatedConfigTypes() []string {
	switch t {
	case ChangeTypeTidb, ChangeTypeDefault:
		return []string{
			ccproto.DEDICATED_TIDB_CONFIG_TIDB,
			ccproto.DEDICATED_TIDB_CONFIG_TIKV,
			ccproto.DEDICATED_TIDB_CONFIG_PD,
			ccproto.DEDICATED_TIDB_CONFIG_TIFLASH,
			ccproto.DEDICATED_TIDB_CONFIG_TIFLASH_LEARNER,
			// ccproto.DEDICATED_TIDB_CONFIG_INITSQL,	unused after cluster create
			ccproto.DEDICATED_TIDB_CONFIG_TICDC,
			ccproto.DEDICATED_TIDB_CONFIG_RESTORE,
			ccproto.DEDICATED_TIDB_CONFIG_BACKUP,
			ccproto.DEDICATED_TIDB_CONFIG_TIDB_LIGHTNING,
			// ccproto.DEDICATED_TIDB_CONFIG_DM, empty config currently
			//  donot update tiproxy when update tidb ccproto.DEDICATED_TIDB_CONFIG_TIPROXY,
		}
	case ChangeTypeTiproxy:
		return []string{
			ccproto.DEDICATED_TIDB_CONFIG_TIPROXY,
		}
	case ChangeTypeDm:
		return []string{
			// ccproto.DEDICATED_TIDB_CONFIG_DM,
		}
	case ChangeTypeFullTidb:
		all := ccproto.ClusterType_DEDICATED_TIDB.AvailableConfigTypes()
		filtered := all[:0]
		for _, ct := range all {
			if ct != ccproto.DEDICATED_TIDB_CONFIG_INITSQL {
				filtered = append(filtered, ct)
			}
		}
		return filtered
	case ChangeTypeChangefeed:
		return ccproto.ClusterType_CHANGEFEED.AvailableConfigTypes()
	case ChangeTypeChangefeedNg:
		return ccproto.ClusterType_CHANGEFEED_NG.AvailableConfigTypes()
	case ChangeTypeChangefeedEssential:
		return ccproto.ClusterType_CHANGEFEED_ESSENTIAL.AvailableConfigTypes()
	case ChangeTypeTiCDC, ChangeTypeTiCDCNg:
		return []string{
			ccproto.TICDC_CONFIG_TICDC,
		}
	case ChangeTypeSharedPool:
		all := ccproto.ClusterType_SHARED_POOL.AvailableConfigTypes()
		filtered := all[:0]
		for _, ct := range all {
			if ct != ccproto.SHARED_POOL_CONFIG_INITSQL {
				filtered = append(filtered, ct)
			}
		}
		return filtered
	case ChangeTypeLogicalCluster:
		return ccproto.ClusterType_LOGICAL_CLUSTER.AvailableConfigTypes()
	}
	return nil
}

func (t ChangeType) ConfigTypeExist(configType string) bool {
	for _, c := range t.RelatedConfigTypes() {
		if c == configType {
			return true
		}
	}
	return false
}

type ListConfigurationChangesParam struct {
	TargetCluster             *ccent.ClusterConfigurationLocator
	TargetTenantInit          *ccent.TenantInitConfigurationLocator
	TargetTemplate            *ccent.ConfigurationTemplateLocator
	IDOnly                    bool
	HaveModifiedParameterLike string
	CreatedAtStartUnixSec     *int64
	CreatedAtEndUnixSec       *int64

	Page    int // default 1
	PerPage int // default 20
}

func (l ListConfigurationChangesParam) Normalize() (ListConfigurationChangesParam, error) {
	if l.Page < 0 {
		l.Page = 1
	}
	if l.PerPage < 0 {
		l.PerPage = 20
	}

	return l, nil
}

type ChangeVersionParam struct {
	ToVersion string `json:"to_version"`
}

type ChangeTemplateParam struct {
	ToTemplateID uint64 `json:"to_template"`
}

type ChangeImageTagParam struct {
	ToImageTag string `json:"to_image_tag"`
}

type ChangeFileParam struct {
	Filename         string                  `json:"filename"`
	ConfigType       string                  `json:"config_type"`
	CustomParameters []*ccent.NamedParameter `json:"custom_parameters"`
	Template         *ChangeTemplateParam    `json:"template"`
	PreviousText     string                  `json:"previous_text"`
}

type ChangeComponentParam struct {
	Component         string               `json:"component"`
	OverwriteImageTag *ChangeImageTagParam `json:"image_tag"`
	Version           *ChangeVersionParam  `json:"version"`
}

type CreateConfigurationChangeBaseParam struct {
	Files           []*ChangeFileParam                       `json:"files"`
	Components      []*ChangeComponentParam                  `json:"components"`
	Version         *ChangeVersionParam                      `json:"version"`
	ChangeType      ChangeType                               `json:"change_type"`
	ExecutionWindow *cmxsvcapi.CreateOpsExecutionWindowParam `json:"execution_window"`

	TargetCluster    *ccent.ClusterConfigurationLocator
	TargetTenantInit *ccent.TenantInitConfigurationLocator
	TargetTemplate   *ccent.ConfigurationTemplateLocator
}

func (c CreateConfigurationChangeBaseParam) FindComponent(component string) (int, *ChangeComponentParam) {
	idx := slices.IndexFunc(c.Components, func(c *ChangeComponentParam) bool {
		return c.Component == component
	})

	if idx < 0 {
		return -1, nil
	}

	return idx, c.Components[idx]
}

func (c CreateConfigurationChangeBaseParam) FindFile(filename string) (int, *ChangeFileParam) {
	idx := slices.IndexFunc(c.Files, func(f *ChangeFileParam) bool {
		return f.Filename == filename
	})

	if idx < 0 {
		return -1, nil
	}

	return idx, c.Files[idx]
}

type CreateConfigurationChangeParam struct {
	CreateConfigurationChangeBaseParam

	Reason         string
	Applicant      string
	OperatorEntity string
	ApplyPolicy    ccent.ParameterApplyPolicy
}

type PreviewConfigurationChangeParam struct {
	CreateConfigurationChangeBaseParam
}

type PreviewConfigurationChangeResult struct {
	Change        *ccent.ConfigurationChange
	Configuration *ccent.Configuration
	ApplyPolicies []ccent.ParameterApplyPolicy
	Reason        string
}

type ApplyConfigurationChangeParam struct {
	Mode          *ApplyMode
	OperatorEmail string
}

type ManagedConfigurationChangeRef struct {
	StepID uint64
}

type CreateManagedConfigurationChangeParam struct {
	Ref    ManagedConfigurationChangeRef
	Change CreateConfigurationChangeParam
}

type ConfigurationChangeService interface {
	ListConfigurationChanges(ctx context.Context, param ListConfigurationChangesParam) ([]*ccent.ConfigurationChange, int64, error)
	GetConfigurationChange(ctx context.Context, id uint64) (*ccent.ConfigurationChange, error)
	PreviewConfigurationChange(ctx context.Context, param PreviewConfigurationChangeParam) (*PreviewConfigurationChangeResult, error)
	CreateConfigurationChange(ctx context.Context, param CreateConfigurationChangeParam) (*uint64, error)
	CreateManagedConfigurationChange(ctx context.Context, param CreateManagedConfigurationChangeParam) (*ccent.ConfigurationChange, error)
	GetManagedConfigurationChangeByStep(ctx context.Context, stepID uint64) (*ccent.ConfigurationChange, error)
	ApplyManagedConfigurationChange(ctx context.Context, ref ManagedConfigurationChangeRef, changeID uint64, mode ApplyConfigurationChangeParam) error
	FailManagedConfigurationChange(ctx context.Context, ref ManagedConfigurationChangeRef, changeID uint64) error
	CancelManagedConfigurationChange(ctx context.Context, ref ManagedConfigurationChangeRef, changeID uint64) error
	CancelConfigurationChange(ctx context.Context, id uint64) error
	ApplyConfigurationChange(ctx context.Context, id uint64, mode ApplyConfigurationChangeParam) error
	ClusterConfigurationChangeable(ctx context.Context, loc ccent.ClusterConfigurationLocator) (string, error)
	TenantInitConfigurationChangeable(ctx context.Context, loc ccent.TenantInitConfigurationLocator) (string, error)
}
