package operatorclient

import (
	"context"
	"reflect"

	"github.com/openshift/library-go/pkg/operator/v1helpers"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/cache"

	operatorv1 "github.com/openshift/api/operator/v1"
	opclient "github.com/openshift/client-go/operator/clientset/versioned"
	opinformers "github.com/openshift/client-go/operator/informers/externalversions"
)

const (
	GlobalConfigName = "cluster"
)

type OperatorClient struct {
	Informers opinformers.SharedInformerFactory
	Client    opclient.Interface
}

var _ v1helpers.OperatorClient = &OperatorClient{}

func (c OperatorClient) Informer() cache.SharedIndexInformer {
	return c.Informers.Operator().V1().Storages().Informer()
}

func (c OperatorClient) GetOperatorState() (*operatorv1.OperatorSpec, *operatorv1.OperatorStatus, string, error) {
	instance, err := c.Informers.Operator().V1().Storages().Lister().Get(GlobalConfigName)
	if err != nil {
		return nil, nil, "", err
	}

	return &instance.Spec.OperatorSpec, &instance.Status.OperatorStatus, instance.ResourceVersion, nil
}

func (c OperatorClient) UpdateOperatorSpec(resourceVersion string, spec *operatorv1.OperatorSpec) (*operatorv1.OperatorSpec, string, error) {
	original, err := c.Informers.Operator().V1().Storages().Lister().Get(GlobalConfigName)
	if err != nil {
		return nil, "", err
	}
	copy := original.DeepCopy()
	copy.ResourceVersion = resourceVersion
	copy.Spec.OperatorSpec = *spec

	ret, err := c.Client.OperatorV1().Storages().Update(context.TODO(), copy, metav1.UpdateOptions{})
	if err != nil {
		return nil, "", err
	}

	return &ret.Spec.OperatorSpec, ret.ResourceVersion, nil
}

func (c OperatorClient) UpdateOperatorStatus(resourceVersion string, status *operatorv1.OperatorStatus) (*operatorv1.OperatorStatus, error) {
	original, err := c.Informers.Operator().V1().Storages().Lister().Get(GlobalConfigName)
	if err != nil {
		return nil, err
	}
	copy := original.DeepCopy()
	copy.ResourceVersion = resourceVersion
	copy.Status.OperatorStatus = *status

	ret, err := c.Client.OperatorV1().Storages().UpdateStatus(context.TODO(), copy, metav1.UpdateOptions{})
	if err != nil {
		return nil, err
	}

	return &ret.Status.OperatorStatus, nil
}

func (c OperatorClient) GetObjectMeta() (meta *metav1.ObjectMeta, err error) {
	instance, err := c.Informers.Operator().V1().Storages().Lister().Get(GlobalConfigName)
	if err != nil {
		return nil, err
	}
	return &instance.ObjectMeta, nil
}

func (c OperatorClient) SetObjectAnnotations(required map[string]string) error {
	instance, err := c.Informers.Operator().V1().Storages().Lister().Get(GlobalConfigName)
	if err != nil {
		return err
	}

	newInstance := instance.DeepCopy()
	for k, v := range required {
		metav1.SetMetaDataAnnotation(&newInstance.ObjectMeta, k, v)
	}
	if !reflect.DeepEqual(instance.Annotations, newInstance.Annotations) {
		_, err := c.Client.OperatorV1().Storages().Update(context.TODO(), newInstance, metav1.UpdateOptions{})
		return err
	}
	return nil
}
