package transfer

import (
	"context"
	"fmt"
	"time"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"

	cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
	"kubevirt.io/containerized-data-importer/pkg/common"
	cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
	"kubevirt.io/containerized-data-importer/pkg/util"
)

type dataVolumeTransferHandler struct {
	objectTransferHandler
}

func (h *dataVolumeTransferHandler) ReconcilePending(ot *cdiv1.ObjectTransfer) (time.Duration, error) {
	dv := &cdiv1.DataVolume{}
	dvExists, err := h.reconciler.getSourceResource(ot, dv)
	if err != nil {
		return 0, h.reconciler.setCompleteConditionError(ot, err)
	}

	if !dvExists {
		// will reconcile again when dv is created/updated
		if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "No source", ""); err != nil {
			return 0, err
		}

		return 0, nil
	}

	if dv.Status.Phase != cdiv1.Succeeded {
		// will reconcile again when dv is updated
		if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Source not populated", ""); err != nil {
			return 0, err
		}

		return 0, nil
	}

	pods, err := cc.GetPodsUsingPVCs(context.TODO(), h.reconciler.Client, dv.Namespace, sets.New(getDataVolumeClaimName(dv)), false)
	if err != nil {
		return 0, h.reconciler.setCompleteConditionError(ot, err)
	}

	if len(pods) > 0 {
		if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Pods using DataVolume PVC", ""); err != nil {
			return 0, err
		}

		return defaultRequeue, nil
	}

	dv2 := dv.DeepCopy()
	dv2.Status = cdiv1.DataVolumeStatus{}
	data := map[string]string{
		"pvcName": getDataVolumeClaimName(dv),
	}

	return 0, h.reconciler.pendingHelper(ot, dv2, data)
}

func (h *dataVolumeTransferHandler) ReconcileRunning(ot *cdiv1.ObjectTransfer) (time.Duration, error) {
	dv := &cdiv1.DataVolume{}
	dvExists, err := h.reconciler.getSourceResource(ot, dv)
	if err != nil {
		return 0, err
	}

	if dvExists {
		return h.deleteDataVolume(ot, dv)
	}

	pvcTransferName := fmt.Sprintf("pvc-transfer-%s", ot.UID)

	pvcTransfer := &cdiv1.ObjectTransfer{}
	pvcTransferExists, err := h.reconciler.getResource("", pvcTransferName, pvcTransfer)
	if err != nil {
		return 0, h.reconciler.setCompleteConditionError(ot, err)
	}

	target := &cdiv1.DataVolume{}
	targetExists, err := h.reconciler.getTargetResource(ot, target)
	if err != nil {
		return 0, h.reconciler.setCompleteConditionError(ot, err)
	}

	pvcName := ot.Status.Data["pvcName"]

	if !targetExists && !pvcTransferExists {
		targetNamespace := getTransferTargetNamespace(ot)
		targetName := getTransferTargetName(ot)

		pvcTransfer = &cdiv1.ObjectTransfer{
			ObjectMeta: metav1.ObjectMeta{
				Name: pvcTransferName,
				Labels: map[string]string{
					common.CDILabelKey:       common.CDILabelValue,
					common.CDIComponentLabel: "",
				},
			},
			Spec: cdiv1.ObjectTransferSpec{
				Source: cdiv1.TransferSource{
					Kind:      "PersistentVolumeClaim",
					Namespace: ot.Spec.Source.Namespace,
					Name:      pvcName,
				},
				Target: cdiv1.TransferTarget{
					Namespace: &targetNamespace,
					Name:      &targetName,
				},
				ParentName: &ot.Name,
			},
		}
		util.SetRecommendedLabels(pvcTransfer, h.reconciler.InstallerLabels, "cdi-controller")

		if err := h.reconciler.Client.Create(context.TODO(), pvcTransfer); err != nil {
			return 0, h.reconciler.setCompleteConditionError(ot, err)
		}

		pvcTransferExists = true
	}

	if pvcTransferExists {
		if pvcTransfer.Status.Phase != cdiv1.ObjectTransferComplete {
			ot.Status.Phase = cdiv1.ObjectTransferRunning
			if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "PVC transfer in progress", ""); err != nil {
				return 0, err
			}

			return 0, nil
		}

		pvc := &corev1.PersistentVolumeClaim{}
		pvcExists, err := h.reconciler.getTargetResource(pvcTransfer, pvc)
		if err != nil {
			return 0, h.reconciler.setCompleteConditionError(ot, err)
		}

		if !pvcExists {
			ot.Status.Phase = cdiv1.ObjectTransferError
			if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Transferred PVC does not exist", ""); err != nil {
				return 0, err
			}

			return 0, nil
		}

		if err := h.addPopulatedAnnotation(ot, pvc); err != nil {
			return 0, h.reconciler.setCompleteConditionError(ot, err)
		}
	}

	if !targetExists {
		target = &cdiv1.DataVolume{}
		if err := h.reconciler.createObjectTransferTarget(ot, target, nil); err != nil {
			return 0, h.reconciler.setCompleteConditionError(ot, err)
		}
	}

	if target.Status.Phase != cdiv1.Succeeded {
		ot.Status.Phase = cdiv1.ObjectTransferRunning
		if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Waiting for target DataVolume", ""); err != nil {
			return 0, err
		}

		return 0, nil
	}

	if pvcTransferExists && pvcTransfer.DeletionTimestamp == nil {
		if err := h.reconciler.Client.Delete(context.TODO(), pvcTransfer); err != nil {
			return 0, h.reconciler.setCompleteConditionError(ot, err)
		}
	}

	ot.Status.Phase = cdiv1.ObjectTransferComplete
	ot.Status.Data = nil
	if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionTrue, "Transfer complete", ""); err != nil {
		return 0, err
	}

	return 0, nil
}

func (h *dataVolumeTransferHandler) deleteDataVolume(ot *cdiv1.ObjectTransfer, dv *cdiv1.DataVolume) (time.Duration, error) {
	pvc := &corev1.PersistentVolumeClaim{}
	pvcExists, err := h.reconciler.getResource(dv.Namespace, getDataVolumeClaimName(dv), pvc)
	if err != nil {
		return 0, h.reconciler.setCompleteConditionError(ot, err)
	}

	if !pvcExists {
		if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Source DV has no PVC", ""); err != nil {
			return 0, err
		}

		return 0, nil
	}

	idx := -1
	for i, o := range pvc.OwnerReferences {
		if o.Kind == "DataVolume" &&
			o.Name == dv.Name &&
			o.UID == dv.UID {
			idx = i
			break
		}
	}

	if idx >= 0 {
		os := pvc.OwnerReferences
		pvc.OwnerReferences = append(os[0:idx], os[idx+1:]...)
	}

	_, ok := pvc.Annotations[cc.AnnPopulatedFor]
	if ok {
		delete(pvc.Annotations, cc.AnnPopulatedFor)
	}

	if idx >= 0 || ok {
		if err := h.reconciler.updateResource(ot, pvc); err != nil {
			return 0, h.reconciler.setCompleteConditionError(ot, err)
		}

		return time.Second, h.reconciler.setCompleteConditionRunning(ot)
	}

	if err := h.reconciler.Client.Delete(context.TODO(), dv); err != nil {
		return 0, h.reconciler.setCompleteConditionError(ot, err)
	}

	return 0, h.reconciler.setCompleteConditionRunning(ot)
}

func (h *dataVolumeTransferHandler) addPopulatedAnnotation(ot *cdiv1.ObjectTransfer, pvc *corev1.PersistentVolumeClaim) error {
	dvName := getTransferTargetName(ot)

	if v, ok := pvc.Annotations[cc.AnnPopulatedFor]; ok {
		if v == dvName {
			return nil
		}

		return fmt.Errorf("PVC populated for a different DataVolume")
	}

	if pvc.Annotations == nil {
		pvc.Annotations = make(map[string]string)
	}

	pvc.Annotations[cc.AnnPopulatedFor] = dvName

	return h.reconciler.updateResource(ot, pvc)
}

func getDataVolumeClaimName(dv *cdiv1.DataVolume) string {
	pvcName, ok := dv.Annotations[cc.AnnPopulatedFor]
	if ok {
		return pvcName
	}

	return dv.Name
}
