package checkpoint

import (
	"context"
	"fmt"
	. "git.jetbrains.team/tch/teamcity-operator/api/v1beta1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	"sigs.k8s.io/controller-runtime/pkg/client"
)

type Checkpoint struct {
	Client       client.Client
	CurrentStage Stage
	Instance     TeamCity
}

func NewCheckpoint(client client.Client, instance TeamCity) *Checkpoint {
	return &Checkpoint{
		Client:       client,
		Instance:     instance,
		CurrentStage: getInitialStageFromInstance(instance),
	}
}

func (c *Checkpoint) DoCheckpointWithDesiredStage(ctx context.Context, desiredStage Stage) error {
	currentStage, err := c.FetchCurrentStageFromCluster(ctx)
	if err != nil {
		if errors.IsNotFound(err) {
			c.CurrentStage = desiredStage
			err = c.Create(ctx)
			if err != nil {
				return err
			}
			return nil
		}
		return err
	}
	c.CurrentStage = currentStage
	canChangeStage, err := c.CurrentStage.canChangeStageValue(desiredStage)
	if err != nil {
		return err
	}
	if canChangeStage {
		c.CurrentStage = desiredStage
		err = c.Update(ctx)
	}
	return nil
}

func (c *Checkpoint) Create(ctx context.Context) error {
	configMap := c.toConfigMapObject()
	if err := c.Client.Create(ctx, &configMap); err != nil {
		return err
	}
	return nil
}
func (c *Checkpoint) UpdateStageFromConfigMap(ctx context.Context) error {
	stage, err := c.FetchCurrentStageFromCluster(ctx)
	if err != nil {
		if errors.IsNotFound(err) {
			initialStage := getInitialStageFromInstance(c.Instance)
			c.CurrentStage = initialStage
			return nil
		}
		return err
	}
	c.CurrentStage = stage
	return nil
}

func (c *Checkpoint) FetchCurrentStageFromCluster(ctx context.Context) (Stage, error) {
	stage := NewStage("")
	configMap, err := c.GetConfigMap(ctx)
	if err != nil {
		return stage, err
	}
	stage, err = GetStageStringValueFromConfigMap(&configMap)
	if err != nil {
		return stage, err
	}
	return stage, nil
}

func (c *Checkpoint) GetConfigMap(ctx context.Context) (v1.ConfigMap, error) {
	var configMap v1.ConfigMap
	namespacedName := c.getNamespacedName()
	if err := c.Client.Get(ctx, namespacedName, &configMap); err != nil {
		return configMap, err
	}
	return configMap, nil
}

func (c *Checkpoint) Update(ctx context.Context) error {
	checkpointCM := c.toConfigMapObject()
	if err := c.Client.Update(ctx, &checkpointCM); err != nil {
		return nil
	}
	return nil
}

func (c *Checkpoint) Delete(ctx context.Context) error {
	configMap, err := c.GetConfigMap(ctx)
	if err != nil {
		if !errors.IsNotFound(err) {
			return err
		}
	}
	if err := c.Client.Delete(ctx, &configMap); err != nil {
		return err
	}

	return nil
}

func (c *Checkpoint) getCheckpointConfigMapName() string {
	return fmt.Sprintf("%s-%s", StageConfigMapNamePrefix, c.Instance.Name)
}

func (c *Checkpoint) getNamespacedName() types.NamespacedName {
	return types.NamespacedName{
		Name:      c.getCheckpointConfigMapName(),
		Namespace: c.Instance.Namespace,
	}
}

func (c *Checkpoint) toConfigMapObject() v1.ConfigMap {
	return v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ConstructCheckpointName(c.Instance.Name),
			Namespace: c.Instance.Namespace,
		},
		Data: map[string]string{
			StageConfigMapKey: c.CurrentStage.String(),
		},
	}
}
