package task

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/types"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	apierrors "k8s.io/apimachinery/pkg/api/errors"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	gitopssdk "github.com/tidbcloud/infra-provider/pkg/gitops-sdk"
	"github.com/tidbcloud/infra-provider/pkg/helm"
)

type crrfTask struct {
	*baseTask
}

func NewCRRF(ctx *Context) deploy.Task {
	return &crrfTask{
		baseTask: &baseTask{Context: ctx},
	}
}

func (t *crrfTask) Name() string {
	return "CRRF"
}

func (t *crrfTask) Create(ctx context.Context) deploy.Result {
	logger := t.logger.WithValues("task", "CRRF")

	// Parse crrf-spec annotation
	specJSON, ok := t.backup.Annotations[cloudv1.BackupAnnotationKeyCRRFSpec]
	if !ok {
		// No spec means nothing to create, but check for orphaned status to clean up
		return t.cleanupOrphanedStatus(ctx)
	}

	var crrfSpec cloudv1.CRRFSpec
	if err := json.Unmarshal([]byte(specJSON), &crrfSpec); err != nil {
		logger.Error(err, "Failed to unmarshal CRRF spec")
		return deploy.Error(fmt.Errorf("failed to unmarshal CRRF spec: %w", err))
	}

	// Parse existing crrf-status if any
	var crrfStatus cloudv1.CRRFStatus
	if statusJSON, ok := t.backup.Annotations[cloudv1.BackupAnnotationKeyCRRFStatus]; ok {
		if err := json.Unmarshal([]byte(statusJSON), &crrfStatus); err != nil {
			logger.Error(err, "Failed to unmarshal CRRF status, resetting")
		}
	}

	if t.envProvider == nil {
		return deploy.Error(fmt.Errorf("envProvider is nil, cannot deploy crrf-daemon via helm"))
	}
	if t.brConfig == nil {
		return deploy.Error(fmt.Errorf("brConfig is nil, cannot determine crrf-daemon chart path"))
	}

	helmConfig, err := t.buildHelmConfig(ctx, &crrfSpec)
	if err != nil {
		return deploy.Error(err)
	}

	crrfStatus = cloudv1.CRRFStatus{
		CRRFID:             crrfSpec.CRRFID,
		Phase:              cloudv1.CRRFPhasePending,
		HelmReleaseName:    helmConfig.GetName(),
		UpstreamStorage:    helmConfig.upstreamStorage,
		DownstreamStorage:  helmConfig.downstreamStorage,
		Image:              helmConfig.image,
		LastTransitionTime: metav1.Now(),
	}
	if err := t.setCRRFStatus(crrfStatus); err != nil {
		return deploy.Error(err)
	}

	// Apply helm chart
	getter := helm.NewClientGetterWrapper(t.envProvider)
	if err := helm.Apply(ctx, getter, helmConfig); err != nil {
		logger.Error(err, "Failed to apply crrf-daemon helm chart")
		crrfStatus.Phase = cloudv1.CRRFPhaseFailed
		crrfStatus.Message = err.Error()
		crrfStatus.LastTransitionTime = metav1.Now()
		_ = t.setCRRFStatus(crrfStatus)
		return deploy.Error(fmt.Errorf("failed to apply crrf-daemon: %w", err))
	}

	return deploy.Done()
}

func (t *crrfTask) PostCreate(ctx context.Context) deploy.Result {
	logger := t.logger.WithValues("task", "CRRF")

	var crrfSpec cloudv1.CRRFSpec
	if specJSON, ok := t.backup.Annotations[cloudv1.BackupAnnotationKeyCRRFSpec]; ok {
		if err := json.Unmarshal([]byte(specJSON), &crrfSpec); err != nil {
			logger.Error(err, "Failed to unmarshal CRRF spec")
			return deploy.Error(fmt.Errorf("failed to unmarshal CRRF spec: %w", err))
		}
	}
	var crrfStatus cloudv1.CRRFStatus
	if statusJSON, ok := t.backup.Annotations[cloudv1.BackupAnnotationKeyCRRFStatus]; ok {
		if err := json.Unmarshal([]byte(statusJSON), &crrfStatus); err != nil {
			logger.Error(err, "Failed to unmarshal CRRF status, resetting")
		}
	}

	helmConfig, err := t.buildHelmConfig(ctx, &crrfSpec)
	if err != nil {
		return deploy.Error(err)
	}

	// Check deployment readiness
	deployment := &appsv1.Deployment{}
	deployKey := types.NamespacedName{
		Namespace: helmConfig.GetNamespace(),
		Name:      helmConfig.GetName(),
	}
	remoteCli := t.envProvider.GetClient()
	if err := remoteCli.Get(ctx, deployKey, deployment); err != nil {
		if apierrors.IsNotFound(err) {
			logger.Info("Waiting for crrf-daemon deployment to be created", "key", deployKey)
			return deploy.Done().WithRequeuAfter(15 * time.Second)
		}
		return deploy.Error(fmt.Errorf("failed to get crrf-daemon deployment: %w", err))
	}

	if deployment.Status.ReadyReplicas > 0 && deployment.Status.ReadyReplicas == deployment.Status.Replicas {
		// Deployment is ready
		crrfStatus.Phase = cloudv1.CRRFPhaseRunning
		crrfStatus.Message = ""
		crrfStatus.UpstreamStorage = helmConfig.upstreamStorage
		crrfStatus.DownstreamStorage = helmConfig.downstreamStorage
		crrfStatus.Image = helmConfig.image
		crrfStatus.LastTransitionTime = metav1.Now()
		if err := t.setCRRFStatus(crrfStatus); err != nil {
			return deploy.Error(err)
		}
		logger.Info("crrf-daemon is running", "release", helmConfig.GetName())
		return deploy.Done()
	}

	logger.Info("Waiting for crrf-daemon deployment to be ready",
		"readyReplicas", deployment.Status.ReadyReplicas,
		"replicas", deployment.Status.Replicas)
	return deploy.Done().WithRequeuAfter(15 * time.Second)
}

func (t *crrfTask) Delete(ctx context.Context) deploy.Result {
	logger := t.logger.WithValues("task", "CRRF")

	// Parse existing crrf-status
	statusJSON, ok := t.backup.Annotations[cloudv1.BackupAnnotationKeyCRRFStatus]
	if !ok {
		// No status, nothing to clean up
		return deploy.Done()
	}

	var crrfStatus cloudv1.CRRFStatus
	if err := json.Unmarshal([]byte(statusJSON), &crrfStatus); err != nil {
		// Can't parse status, remove it and move on
		delete(t.backup.Annotations, cloudv1.BackupAnnotationKeyCRRFStatus)
		return deploy.Done()
	}

	if t.envProvider == nil {
		logger.Info("envProvider is nil, skipping helm uninstall")
		return deploy.Done()
	}

	// Set status to Destroying
	if crrfStatus.Phase != cloudv1.CRRFPhaseDestroying {
		crrfStatus.Phase = cloudv1.CRRFPhaseDestroying
		crrfStatus.LastTransitionTime = metav1.Now()
		_ = t.setCRRFStatus(crrfStatus)
	}

	// Uninstall helm release
	releaseName := crrfStatus.HelmReleaseName
	if releaseName == "" {
		releaseName = fmt.Sprintf("crrf-daemon-%s", t.backup.Name)
	}

	if t.brConfig != nil {
		helmConfig := &CRRFDaemonConfig{
			chartPath: t.brConfig.CRRFDaemon.ChartPath,
			backup:    t.backup,
			cluster:   t.cluster,
		}
		getter := helm.NewClientGetterWrapper(t.envProvider)
		if err := helm.Uninstall(ctx, getter, helmConfig, releaseName); err != nil {
			logger.Error(err, "Failed to uninstall crrf-daemon helm release", "release", releaseName)
			return deploy.Error(fmt.Errorf("failed to uninstall crrf-daemon: %w", err))
		}
	}

	// Remove crrf-status annotation
	delete(t.backup.Annotations, cloudv1.BackupAnnotationKeyCRRFStatus)
	logger.Info("crrf-daemon uninstalled and status cleaned up", "release", releaseName)
	return deploy.Done()
}

// cleanupOrphanedStatus handles the case where crrf-spec was removed but crrf-status still exists.
// This triggers the teardown of the crrf-daemon.
func (t *crrfTask) cleanupOrphanedStatus(ctx context.Context) deploy.Result {
	if _, ok := t.backup.Annotations[cloudv1.BackupAnnotationKeyCRRFStatus]; !ok {
		return deploy.Done()
	}
	// No spec but status exists: trigger delete to clean up
	return t.Delete(ctx)
}

// setCRRFStatus marshals the CRRFStatus and sets it as annotation on the backup.
func (t *crrfTask) setCRRFStatus(status cloudv1.CRRFStatus) error {
	data, err := json.Marshal(status)
	if err != nil {
		return fmt.Errorf("failed to marshal CRRF status: %w", err)
	}
	if t.backup.Annotations == nil {
		t.backup.Annotations = make(map[string]string)
	}
	t.backup.Annotations[cloudv1.BackupAnnotationKeyCRRFStatus] = string(data)
	return nil
}

func (t *crrfTask) buildHelmConfig(ctx context.Context, crrfSpec *cloudv1.CRRFSpec) (*CRRFDaemonConfig, error) {
	upstreamStorage := t.backup.Status.BackupPath
	if upstreamStorage == "" {
		return nil, fmt.Errorf("backup.Status.BackupPath is empty, cannot determine upstream storage")
	}

	// Determine image: use spec override or fallback to backup configuration
	image := crrfSpec.Image
	if image == "" && t.backup.Spec.Configuration != nil {
		if t.backup.Spec.Configuration.BaseImage != "" {
			image = gitopssdk.OverrideBaseImage(t.backup, t.backup.Spec.Configuration.BaseImage)
			if t.backup.Spec.Configuration.Tag != "" {
				image = fmt.Sprintf("%s:%s", image, t.backup.Spec.Configuration.Tag)
			}
		}
	}

	tidbNs := t.clusterNamer.GetTiDBNamespace()
	pdAddr := fmt.Sprintf("https://%s-pd.%s.svc:2379", t.clusterNamer.GetTiDBName(), tidbNs)

	c := &CRRFDaemonConfig{
		chartPath:         t.brConfig.CRRFDaemon.ChartPath,
		backup:            t.backup,
		cluster:           t.cluster,
		crrfID:            crrfSpec.CRRFID,
		upstreamStorage:   upstreamStorage,
		downstreamStorage: crrfSpec.DownstreamStorage,
		image:             image,
		taskName:          t.backup.Name,
		pdAddr:            pdAddr,
	}

	return c, nil
}
