package task

import (
	"context"
	"fmt"

	vmoperatorv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	"go.uber.org/multierr"
	"k8s.io/apimachinery/pkg/api/meta"
	yaml "sigs.k8s.io/yaml/goyaml.v3"

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/controllers/kubernetesagent/metadata"
	"github.com/tidbcloud/observability/agent-control-server/pkg/agentbuild"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/deploy"
	"github.com/tidbcloud/observability/agent-control-server/pkg/helmcli"
	"github.com/tidbcloud/observability/agent-control-server/pkg/reconciler"
)

type OperatorTask struct {
	*reconciler.BaseTask[*agentv2.KubernetesAgent]
}

func NewOperatorTask(c *reconciler.Context[*agentv2.KubernetesAgent]) *OperatorTask {
	return &OperatorTask{
		BaseTask: &reconciler.BaseTask[*agentv2.KubernetesAgent]{Context: c},
	}
}

// helmReleaseLabels scopes prune to this controller's own releases; Vector
// and anything else sharing the namespace stay untouched.
var helmReleaseLabels = map[string]string{
	"o11y.tidbcloud.com/managed-by": "kubernetesagent",
}

func (t *OperatorTask) Name() string {
	return "Operator"
}

func (t *OperatorTask) ClusterMeta() deploy.ClusterMeta {
	return metadata.ClusterMeta(t.Agent)
}

func (t *OperatorTask) Create(ctx context.Context) deploy.Result {
	helm, err := helmcli.BuildHelmClient(ctx, t.Remote, t.buildNamespace())
	if err != nil {
		return deploy.Panic(err)
	}
	for name, chart := range t.Config.O11yConfig.CustomerConfigsV2.NextGen.Charts {
		var valuesYaml []byte
		if len(chart.Values) > 0 {
			var err error
			valuesYaml, err = yaml.Marshal(chart.Values)
			if err != nil {
				return deploy.Panic(fmt.Errorf("failed to marshal values: %w", err))
			}
		}

		t.L.Info(fmt.Sprintf("InstallOrUpgrade chart %s with version %s", chart.Name, chart.Version))
		if err := helmcli.InstallOrUpgradeChart(
			ctx,
			helm,
			name,
			chart.Name,
			t.buildNamespace(),
			chart.Version,
			string(valuesYaml),
			helmReleaseLabels,
		); err != nil {
			return deploy.Panic(err)
		}
	}

	keep := make(map[string]struct{})
	for name := range t.Config.O11yConfig.CustomerConfigsV2.NextGen.Charts {
		keep[name] = struct{}{}
	}
	uninstalled, err := helmcli.UninstallUnknownReleases(ctx, helm, t.buildNamespace(), helmReleaseLabels, keep)
	if err != nil {
		// Cleanup failures (hook timeouts, finalizer stalls, ...) must not block
		// the rest of the reconcile pipeline.
		return deploy.Error(err)
	}
	if len(uninstalled) > 0 {
		t.L.Info(fmt.Sprintf("uninstalled stale helm releases: %v", uninstalled))
	}
	return deploy.Done()
}

func (t *OperatorTask) PostCreate(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	for name := range t.Config.O11yConfig.CustomerConfigsV2.NextGen.Charts {
		if err := agentbuild.CheckDeploymentReady(ctx, cli, name, t.buildNamespace()); err != nil {
			return deploy.Panic(err)
		}
	}
	return deploy.Done()
}

func (t *OperatorTask) Delete(ctx context.Context) deploy.Result {
	if t.Config.IsServerlessMode() {
		return t.deleteServerless(ctx)
	}
	return t.deleteNative(ctx)
}

func (t *OperatorTask) deleteServerless(ctx context.Context) deploy.Result {
	var errs error
	for name, chart := range t.Config.O11yConfig.CustomerConfigsV2.NextGen.Charts {
		t.L.Info(fmt.Sprintf("Uninstall chart %s with version %s", chart.Name, chart.Version))
		if err := agentbuild.DeleteHelm(ctx, t.Remote, name, t.buildNamespace()); err != nil {
			errs = multierr.Append(errs, err)
		}
	}

	if errs != nil {
		return deploy.Panic(errs)
	}
	return deploy.Done()
}

func (t *OperatorTask) deleteNative(ctx context.Context) deploy.Result {
	// Ensure all vmagent CRs are deleted before operator is removed
	// This prevents PVC leaks by ensuring proper cleanup
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	var vmagents vmoperatorv1beta1.VMAgentList
	err = cli.List(ctx, &vmagents)
	if err != nil {
		if meta.IsNoMatchError(err) {
			return deploy.Done() // CRD not installed, nothing to clean up
		}
		return deploy.Panic(err)
	}

	for _, vmagent := range vmagents.Items {
		if err := agentbuild.DeleteVMAgent(ctx, cli, vmagent.Name, vmagent.Namespace); err != nil {
			return deploy.Panic(err)
		}
	}

	return deploy.Done()
}

func (t *OperatorTask) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *OperatorTask) buildNamespace() string {
	if t.Config.IsServerlessMode() {
		return consts.ServerlessObservabilityNamespace
	}
	return consts.O11ySystemNamespace
}
