package network

import (
	"context"
	"errors"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	"github.com/tidbcloud/shared-provider/pkg/utils/namespace"
)

var errNamespaceIsNotOwned = errors.New("namespace is not owned by network")

func (r *Reconciler) syncNamespace(ctx context.Context, net *cloudv1.Network) error {
	name := namespace.GetOwnedNamespaceNameFromNetwork(net)
	ns := &corev1.Namespace{}

	if err := r.Cache.Get(ctx, client.ObjectKey{Name: name}, ns); err != nil {
		if apierrors.IsNotFound(err) {
			newNs := namespace.NewOwnedNamespace(net, nil)
			newNs.Labels = labels.NewKubeClusterLabels(newNs.Labels, r.KubeClusterName)

			if nerr := r.Client.Create(ctx, newNs); nerr != nil {
				return fmt.Errorf("can't create namespace %s: %w", name, nerr)
			}

			return nil
		}

		return fmt.Errorf("can't get namespace %s: %w", name, err)
	}

	isLabelChanged := false

	c := labels.GetKubeClusterFromLabels(ns.Labels)
	if c != r.KubeClusterName {
		ns.Labels = labels.NewKubeClusterLabels(ns.Labels, r.KubeClusterName)
		isLabelChanged = true
	}

	owned, matched := labels.MatchOwnerLabels(net, ns.Labels)
	if !matched {
		isLabelChanged = true
	}

	if !owned {
		r.InfraRecorder.Eventf(net,
			corev1.EventTypeWarning,
			"NamespaceIsNotOwned",
			"can't sync namespace %s, because it is not owned by %s", ns.Name, client.ObjectKeyFromObject(net))

		// TODO: it can't be resynced, a more reasonable operation is needed ?
		return fmt.Errorf("namespace %s, network %s: %w", ns.Name, client.ObjectKeyFromObject(net), errNamespaceIsNotOwned)
	}

	if isLabelChanged {
		ns = namespace.NewOwnedNamespace(net, ns)
		// if labels is owned but not matched, just update them
		// TODO: delete then create ?
		if err := r.Client.Update(ctx, ns); err != nil {
			return fmt.Errorf("can't update namespace %s: %w", ns.Name, err)
		}
	}

	if !namespace.HasOwnedNamespaceName(net) {
		// TODO: use status to store namespace ?
		namespace.SetOwnedNamespaceNameInNetwork(net, ns.Name)

		if err := r.InfraClient.Update(ctx, net); err != nil {
			return fmt.Errorf(
				"add namespace %s annotation into network %s failed: %w",
				ns.Name,
				client.ObjectKeyFromObject(net),
				err,
			)
		}
	}

	return nil
}
