package config

import (
	"context"
	"fmt"
	"slices"
	"time"

	ccproto "github.com/tidbcloud/config-center/proto/config_center"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/cvt"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/repo"
)

func PatchConfigurationChangeToClose(phase ccent.ConfigurationChangePhase) repo.PatchConfigurationChange {
	return func(current *ccent.ConfigurationChange) error {
		current.ClosedAt = pointer.Pointer(time.Now().Unix())
		current.Phase = phase
		return nil
	}
}

func DetailLink(chgID uint64) string {
	return fmt.Sprintf("https://%s/confighistories/%d", consts.ConsoleAddr(), chgID)
}

func ClusterConfigurationChangeable(ctx context.Context, comps Components, loc ccent.ClusterConfigurationLocator) (string, error) {
	opts := []repoopt.Option{
		repo.ConfigurationChangeOpts.TargetClusterIs(loc),
		repo.ConfigurationChangeOpts.PhaseNotIn(ccent.ConfigurationChangePhaseFinishedValues),
	}
	prevChgs, err := comps.Repo.ListConfigurationChanges(ctx, opts...)
	if err != nil {
		return "", fmt.Errorf("failed to list previous cluster configuration changes: %w", err)
	}
	if !clusterConfigurationChangesAllowSomeNewChange(prevChgs) {
		return "some cluster configuration changes are already in progress", nil
	}

	return "", nil
}

type clusterConfigurationChangeScope int

const (
	clusterConfigurationChangeScopeUnknown clusterConfigurationChangeScope = iota
	clusterConfigurationChangeScopeTiProxyOnly
	clusterConfigurationChangeScopeNonTiProxyOnly
)

func ClusterConfigurationChangeableForChange(ctx context.Context, comps Components, newChange *ccent.ConfigurationChange) (string, error) {
	if newChange == nil || newChange.TargetCluster == nil {
		return "", nil
	}

	opts := []repoopt.Option{
		repo.ConfigurationChangeOpts.TargetClusterIs(*newChange.TargetCluster),
		repo.ConfigurationChangeOpts.PhaseNotIn(ccent.ConfigurationChangePhaseFinishedValues),
	}
	prevChgs, err := comps.Repo.ListConfigurationChanges(ctx, opts...)
	if err != nil {
		return "", fmt.Errorf("failed to list previous cluster configuration changes: %w", err)
	}

	newScope := classifyClusterConfigurationChangeScope(newChange)
	for _, prevChg := range prevChgs {
		prevScope := classifyClusterConfigurationChangeScope(prevChg)
		if clusterConfigurationChangesConflict(prevScope, newScope) {
			return "some cluster configuration changes are already in progress", nil
		}
	}

	return "", nil
}

func classifyClusterConfigurationChangeScope(chg *ccent.ConfigurationChange) clusterConfigurationChangeScope {
	if chg == nil || chg.TargetCluster == nil {
		return clusterConfigurationChangeScopeUnknown
	}
	if chg.TargetCluster.ClusterType != ccent.ClusterTypeLogicalCluster &&
		chg.TargetCluster.ClusterType != ccent.ClusterTypeSharedPool {
		return clusterConfigurationChangeScopeUnknown
	}
	if chg.Scope.Version != nil {
		return clusterConfigurationChangeScopeUnknown
	}

	clusterType := cvt.To.ConfigCenter.ClusterType(chg.TargetCluster.ClusterType)
	seenTiProxy := false
	seenNonTiProxy := false
	seenAny := false
	for _, file := range chg.Scope.Files {
		component := clusterType.ConfigType2Component(file.ConfigType)
		if component == "" {
			return clusterConfigurationChangeScopeUnknown
		}
		seenAny = true
		if component == ccproto.SHARED_POOL_COMPONENT_TIPROXY {
			seenTiProxy = true
		} else {
			seenNonTiProxy = true
		}
	}
	for _, componentChange := range chg.Scope.Components {
		component := componentChange.Component
		if component == "" || !slices.Contains(clusterType.AvailableComponents(), component) {
			return clusterConfigurationChangeScopeUnknown
		}
		seenAny = true
		if component == ccproto.SHARED_POOL_COMPONENT_TIPROXY {
			seenTiProxy = true
		} else {
			seenNonTiProxy = true
		}
	}

	if !seenAny || seenTiProxy == seenNonTiProxy {
		return clusterConfigurationChangeScopeUnknown
	}
	if seenTiProxy {
		return clusterConfigurationChangeScopeTiProxyOnly
	}
	return clusterConfigurationChangeScopeNonTiProxyOnly
}

func clusterConfigurationChangesConflict(a, b clusterConfigurationChangeScope) bool {
	if a == clusterConfigurationChangeScopeTiProxyOnly && b == clusterConfigurationChangeScopeNonTiProxyOnly {
		return false
	}
	if a == clusterConfigurationChangeScopeNonTiProxyOnly && b == clusterConfigurationChangeScopeTiProxyOnly {
		return false
	}
	return true
}

func clusterConfigurationChangesAllowSomeNewChange(prevChgs []*ccent.ConfigurationChange) bool {
	hasTiProxyOnly := false
	hasNonTiProxyOnly := false
	for _, prevChg := range prevChgs {
		switch classifyClusterConfigurationChangeScope(prevChg) {
		case clusterConfigurationChangeScopeTiProxyOnly:
			hasTiProxyOnly = true
		case clusterConfigurationChangeScopeNonTiProxyOnly:
			hasNonTiProxyOnly = true
		default:
			return false
		}
		if hasTiProxyOnly && hasNonTiProxyOnly {
			return false
		}
	}
	return true
}

func TenantInitConfigurationChangeable(ctx context.Context, comps Components, loc ccent.TenantInitConfigurationLocator) (string, error) {
	// check if there is any other configuration change in progress to avoid conflict
	opts := []repoopt.Option{
		repo.ConfigurationChangeOpts.TargetTenantInitIs(loc),
		repo.ConfigurationChangeOpts.PhaseNotIn(ccent.ConfigurationChangePhaseFinishedValues),
	}
	prevChgs, err := comps.Repo.ListConfigurationChanges(ctx, opts...)
	if err != nil {
		return "", fmt.Errorf("failed to list previous tenant-init configuration changes: %w", err)
	}
	if len(prevChgs) != 0 {
		return "some tenant-init configuration changes are already in progress", nil
	}

	return "", nil
}
