package management

import (
	"context"
	"fmt"

	"github.com/aws/eks-anywhere/pkg/cluster"
	"github.com/aws/eks-anywhere/pkg/filewriter"
	"github.com/aws/eks-anywhere/pkg/logger"
	"github.com/aws/eks-anywhere/pkg/providers"
	"github.com/aws/eks-anywhere/pkg/task"
	"github.com/aws/eks-anywhere/pkg/types"
	"github.com/aws/eks-anywhere/pkg/validations"
	"github.com/aws/eks-anywhere/pkg/workflows"
	"github.com/aws/eks-anywhere/pkg/workflows/interfaces"
	v1releasealpha1 "github.com/aws/eks-anywhere/release/api/v1alpha1"
)

// UpgradeManagementComponentsWorkflow is a schema for upgrade management components.
type UpgradeManagementComponentsWorkflow struct {
	clientFactory  interfaces.ClientFactory
	provider       providers.Provider
	clusterManager interfaces.ClusterManager
	gitOpsManager  interfaces.GitOpsManager
	writer         filewriter.FileWriter
	capiManager    interfaces.CAPIManager
	eksdInstaller  interfaces.EksdInstaller
	eksdUpgrader   interfaces.EksdUpgrader
}

// NewUpgradeManagementComponentsRunner builds a new UpgradeManagementCommponents construct.
func NewUpgradeManagementComponentsRunner(
	clientFactory interfaces.ClientFactory,
	provider providers.Provider,
	capiManager interfaces.CAPIManager,
	clusterManager interfaces.ClusterManager,
	gitOpsManager interfaces.GitOpsManager,
	writer filewriter.FileWriter,
	eksdUpgrader interfaces.EksdUpgrader,
	eksdInstaller interfaces.EksdInstaller,
) *UpgradeManagementComponentsWorkflow {
	return &UpgradeManagementComponentsWorkflow{
		clientFactory:  clientFactory,
		provider:       provider,
		clusterManager: clusterManager,
		gitOpsManager:  gitOpsManager,
		writer:         writer,
		capiManager:    capiManager,
		eksdUpgrader:   eksdUpgrader,
		eksdInstaller:  eksdInstaller,
	}
}

// UMCValidator is a struct that holds a cluster and a kubectl executable.
// It is used to perform preflight validations on the cluster.
type UMCValidator struct {
	cluster         *types.Cluster
	eksaRelease     *v1releasealpha1.EKSARelease
	kubectl         validations.KubectlClient
	skipValidations []string
}

// NewUMCValidator is a constructor function that creates a new instance of UMCValidator.
func NewUMCValidator(cluster *types.Cluster, eksaRelease *v1releasealpha1.EKSARelease, kubectl validations.KubectlClient, skipValidations []string) *UMCValidator {
	return &UMCValidator{
		cluster:         cluster,
		eksaRelease:     eksaRelease,
		kubectl:         kubectl,
		skipValidations: skipValidations,
	}
}

// PreflightValidations is a method of the UMCValidator struct.
// It performs preflight validations on the cluster and returns a slice of Validation objects.
func (u *UMCValidator) PreflightValidations(ctx context.Context) []validations.Validation {
	preflightValidations := []validations.Validation{
		func() *validations.ValidationResult {
			return &validations.ValidationResult{
				Name:        "control plane ready",
				Remediation: fmt.Sprintf("ensure control plane nodes and pods for cluster %s are Ready", u.cluster.Name),
				Err:         u.kubectl.ValidateControlPlaneNodes(ctx, u.cluster, u.cluster.Name),
			}
		},
		func() *validations.ValidationResult {
			return &validations.ValidationResult{
				Name:        "cluster CRDs ready",
				Remediation: "",
				Err:         u.kubectl.ValidateClustersCRD(ctx, u.cluster),
			}
		},
	}

	var skipVersionSkew bool

	// When upgrading management components, the only preflight check that can be skipped
	// is the EKS-A version skew validation. While the skipValidations flag accepts multiple
	// values for consistency with the cluster upgrade command, only the EKS-A version skew
	// check (validations.EksaVersionSkew) will be honored - all other skip requests will
	// be ignored.
	if len(u.skipValidations) != 0 {
		for _, val := range u.skipValidations {
			if val == validations.EksaVersionSkew {
				skipVersionSkew = true
			}
		}
	}
	if !skipVersionSkew {
		eksaVersionSkewValidation := func() *validations.ValidationResult {
			return &validations.ValidationResult{
				Name:        "validate compatibility of management components version to cluster eksaVersion",
				Remediation: "",
				Err:         validations.ValidateManagementComponentsVersionSkew(ctx, u.kubectl, u.cluster, u.eksaRelease),
			}
		}
		preflightValidations = append(preflightValidations, eksaVersionSkewValidation)
	}

	return preflightValidations
}

// Run Upgrade implements upgrade functionality for management cluster's upgrade operation.
func (umc *UpgradeManagementComponentsWorkflow) Run(ctx context.Context, clusterSpec *cluster.Spec, managementCluster *types.Cluster, validator interfaces.Validator) error {
	commandContext := &task.CommandContext{
		ClientFactory:     umc.clientFactory,
		Provider:          umc.provider,
		ClusterManager:    umc.clusterManager,
		ManagementCluster: managementCluster,
		ClusterSpec:       clusterSpec,
		Validations:       validator,
		Writer:            umc.writer,
		CAPIManager:       umc.capiManager,
		UpgradeChangeDiff: types.NewChangeDiff(),
		GitOpsManager:     umc.gitOpsManager,
		EksdUpgrader:      umc.eksdUpgrader,
		EksdInstaller:     umc.eksdInstaller,
	}

	return task.NewTaskRunner(&setupAndValidateMC{}, umc.writer).RunTask(ctx, commandContext)
}

type setupAndValidateMC struct{}

// Run setupAndValidate validates management cluster before upgrade process starts.
func (s *setupAndValidateMC) Run(ctx context.Context, commandContext *task.CommandContext) task.Task {
	logger.Info("Performing setup and validations")
	currentSpec, err := commandContext.ClusterManager.GetCurrentClusterSpec(ctx, commandContext.ManagementCluster, commandContext.ClusterSpec.Cluster.Name)
	if err != nil {
		commandContext.SetError(err)
		return nil
	}
	commandContext.CurrentClusterSpec = currentSpec
	runner := validations.NewRunner()
	runner.Register(
		func() *validations.ValidationResult {
			return &validations.ValidationResult{
				Name: fmt.Sprintf("%s provider setup and validation", commandContext.Provider.Name()),
				Err:  commandContext.Provider.SetupAndValidateUpgradeManagementComponents(ctx, commandContext.ClusterSpec),
			}
		},
	)
	runner.Register(commandContext.Validations.PreflightValidations(ctx)...)

	err = runner.Run()
	if err != nil {
		commandContext.SetError(err)
		return nil
	}

	return &upgradeCoreComponentsMC{
		UpgradeChangeDiff: &types.ChangeDiff{},
	}
}

func (s *setupAndValidateMC) Name() string {
	return "validate"
}

func (s *setupAndValidateMC) Restore(_ context.Context, _ *task.CommandContext, _ *task.CompletedTask) (task.Task, error) {
	return nil, nil
}

func (s *setupAndValidateMC) Checkpoint() *task.CompletedTask {
	return &task.CompletedTask{
		Checkpoint: nil,
	}
}

// This struct is similar to upgradeCoreComponents, but its returned value is different in Run() function.
type upgradeCoreComponentsMC struct {
	UpgradeChangeDiff *types.ChangeDiff
}

func (s *upgradeCoreComponentsMC) Name() string {
	return "upgrade-core-components-mc"
}

func (s *upgradeCoreComponentsMC) Checkpoint() *task.CompletedTask {
	return &task.CompletedTask{
		Checkpoint: s.UpgradeChangeDiff,
	}
}

func (s *upgradeCoreComponentsMC) Restore(_ context.Context, commandContext *task.CommandContext, completedTask *task.CompletedTask) (task.Task, error) {
	s.UpgradeChangeDiff = &types.ChangeDiff{}
	if err := task.UnmarshalTaskCheckpoint(completedTask.Checkpoint, s.UpgradeChangeDiff); err != nil {
		return nil, err
	}
	commandContext.UpgradeChangeDiff = s.UpgradeChangeDiff
	return &installNewComponentsMC{}, nil
}

func (s *upgradeCoreComponentsMC) Run(ctx context.Context, commandContext *task.CommandContext) task.Task {
	if err := runUpgradeCoreComponents(ctx, commandContext); err != nil {
		return &workflows.CollectMgmtClusterDiagnosticsTask{}
	}
	return &installNewComponentsMC{}
}

// This struct is similar to installNewComponents, but its returned value is different in Run() function.
type installNewComponentsMC struct{}

func (s *installNewComponentsMC) Run(ctx context.Context, commandContext *task.CommandContext) task.Task {
	if err := runInstallNewComponents(ctx, commandContext); err != nil {
		return &workflows.CollectMgmtClusterDiagnosticsTask{}
	}

	if commandContext.OriginalError == nil {
		logger.MarkSuccess("Management components upgraded!")
	}

	return nil
}

func (s *installNewComponentsMC) Name() string {
	return "install-new-eksa-version-components-mc"
}

func (s *installNewComponentsMC) Checkpoint() *task.CompletedTask {
	return &task.CompletedTask{
		Checkpoint: nil,
	}
}

func (s *installNewComponentsMC) Restore(_ context.Context, _ *task.CommandContext, _ *task.CompletedTask) (task.Task, error) {
	return nil, nil
}
