package flux

import (
	"context"
	"fmt"

	"github.com/aws/eks-anywhere/pkg/cluster"
	"github.com/aws/eks-anywhere/pkg/logger"
	"github.com/aws/eks-anywhere/pkg/types"
)

const upgradeFluxconfigCommitMessage = "Upgrade commit of flux configuration; generated by EKS-A CLI"

// Upgrade checks whether upgrading Flux components is necessary and, if so, upgrades to them to the new versions.
func (f *Flux) Upgrade(ctx context.Context, managementCluster *types.Cluster, currentManagementComponents, newManagementComponents *cluster.ManagementComponents, currentSpec *cluster.Spec, newSpec *cluster.Spec) (*types.ChangeDiff, error) {
	logger.V(1).Info("Checking for Flux upgrades")

	changeDiff := ChangeDiff(currentManagementComponents, newManagementComponents, currentSpec, newSpec)
	if changeDiff == nil {
		logger.V(1).Info("Nothing to upgrade for Flux")
		return nil, nil
	}

	logger.V(1).Info("Starting Flux upgrades")
	if err := f.upgradeFilesAndCommit(ctx, newManagementComponents, newSpec); err != nil {
		return nil, fmt.Errorf("upgrading Flux from version %s to %s: %v", currentManagementComponents.Flux.Version, newManagementComponents.Flux.Version, err)
	}
	if err := f.fluxClient.DeleteSystemSecret(ctx, managementCluster, newSpec.FluxConfig.Spec.SystemNamespace); err != nil {
		return nil, fmt.Errorf("upgrading Flux when deleting old flux-system secret: %v", err)
	}
	if err := f.BootstrapGithub(ctx, managementCluster, newSpec); err != nil {
		return nil, fmt.Errorf("upgrading Flux components with github provider: %v", err)
	}
	if err := f.BootstrapGit(ctx, managementCluster, newSpec); err != nil {
		return nil, fmt.Errorf("upgrading Flux components with git provider: %v", err)
	}
	if err := f.fluxClient.Reconcile(ctx, managementCluster, newSpec.FluxConfig); err != nil {
		return nil, fmt.Errorf("reconciling Flux components: %v", err)
	}

	return changeDiff, nil
}

// ChangeDiff generates a version change diff for the Flux components.
func ChangeDiff(currentManagementComponents, newManagementComponents *cluster.ManagementComponents, currentSpec, newSpec *cluster.Spec) *types.ChangeDiff {
	if !newSpec.Cluster.IsSelfManaged() {
		logger.V(1).Info("Skipping Flux upgrades, not a self-managed cluster")
		return nil
	}
	if currentSpec.Cluster.Spec.GitOpsRef == nil && newSpec.Cluster.Spec.GitOpsRef != nil {
		logger.V(1).Info("Skipping Flux upgrades, no previous flux installed in the cluster")
		return nil
	}
	if currentSpec.Cluster.Spec.GitOpsRef == nil {
		logger.V(1).Info("Skipping Flux upgrades, GitOps not enabled")
		return nil
	}
	oldVersion := currentManagementComponents.Flux.Version
	newVersion := newManagementComponents.Flux.Version
	if oldVersion != newVersion {
		logger.V(1).Info("Flux change diff ", "oldVersion ", oldVersion, "newVersion ", newVersion)
		return &types.ChangeDiff{
			ComponentReports: []types.ComponentChangeDiff{
				{
					ComponentName: "Flux",
					NewVersion:    newVersion,
					OldVersion:    oldVersion,
				},
			},
		}
	}
	return nil
}

// Install installs the Flux components.
func (f *Flux) Install(ctx context.Context, cluster *types.Cluster, managementComponents *cluster.ManagementComponents, oldSpec, newSpec *cluster.Spec) error {
	if oldSpec.Cluster.Spec.GitOpsRef == nil && newSpec.Cluster.Spec.GitOpsRef != nil {
		return f.InstallGitOps(ctx, cluster, managementComponents, newSpec, nil, nil)
	}
	return nil
}

func (f *Flux) upgradeFilesAndCommit(ctx context.Context, managementComponents *cluster.ManagementComponents, newSpec *cluster.Spec) error {
	fc := &fluxForCluster{
		Flux:        f,
		clusterSpec: newSpec,
	}

	if err := fc.syncGitRepo(ctx); err != nil {
		return err
	}

	if err := fc.commitFluxUpgradeFilesToGit(ctx, managementComponents); err != nil {
		return err
	}

	return nil
}

func (fc *fluxForCluster) commitFluxUpgradeFilesToGit(ctx context.Context, managementComponents *cluster.ManagementComponents) error {
	logger.Info("Adding flux configuration files to Git")

	g := NewFileGenerator()
	if err := g.Init(fc.writer, fc.eksaSystemDir(), fc.fluxSystemDir()); err != nil {
		return err
	}

	if err := g.WriteFluxKustomization(managementComponents, fc.clusterSpec); err != nil {
		return err
	}

	if err := fc.gitClient.Add(fc.path()); err != nil {
		return fmt.Errorf("adding %s to git: %v", fc.path(), err)
	}

	if err := fc.Flux.pushToRemoteRepo(ctx, fc.path(), upgradeFluxconfigCommitMessage); err != nil {
		return err
	}
	logger.V(3).Info("Finished pushing flux custom manifest files to git",
		"repository", fc.repository())
	return nil
}
