/*
Copyright 2026.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
	"context"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	logf "sigs.k8s.io/controller-runtime/pkg/log"

	lmcachev1alpha1 "github.com/LMCache/LMCache/api/v1alpha1"
	"github.com/LMCache/LMCache/internal/resources"
)

const finalizerName = "lmcache.ai/cleanup"

// LMCacheEngineReconciler reconciles a LMCacheEngine object.
type LMCacheEngineReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

// +kubebuilder:rbac:groups=lmcache.lmcache.ai,resources=lmcacheengines,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=lmcache.lmcache.ai,resources=lmcacheengines/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=lmcache.lmcache.ai,resources=lmcacheengines/finalizers,verbs=update
// +kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete

// Reconcile reconciles the LMCacheEngine CR.
func (r *LMCacheEngineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := logf.FromContext(ctx)

	// 1. Fetch CR
	engine := &lmcachev1alpha1.LMCacheEngine{}
	if err := r.Get(ctx, req.NamespacedName, engine); err != nil {
		if apierrors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	// 2. Handle finalizer
	if err, done := r.handleFinalizer(ctx, engine); done {
		return ctrl.Result{}, err
	}

	// 3. Set defaults
	engine.SetDefaults()

	// 4. Validate
	if err := r.validateAndSetCondition(ctx, engine); err != nil {
		return ctrl.Result{}, err
	}

	// 5. Reconcile DaemonSet
	if err := r.reconcileDaemonSet(ctx, engine); err != nil {
		log.Error(err, "Failed to reconcile DaemonSet")
		return ctrl.Result{}, err
	}

	// 6. Reconcile lookup Service (node-local discovery for vLLM)
	if err := r.reconcileLookupService(ctx, engine); err != nil {
		log.Error(err, "Failed to reconcile lookup Service")
		return ctrl.Result{}, err
	}

	// 7. Reconcile metrics Service
	if err := r.reconcileMetricsService(ctx, engine); err != nil {
		log.Error(err, "Failed to reconcile metrics Service")
		return ctrl.Result{}, err
	}

	// 8. Reconcile ConfigMap
	if err := r.reconcileConnectionConfigMap(ctx, engine); err != nil {
		log.Error(err, "Failed to reconcile ConfigMap")
		return ctrl.Result{}, err
	}

	// 9. Reconcile ServiceMonitor
	if err := r.reconcileServiceMonitor(ctx, engine); err != nil {
		log.Error(err, "Failed to reconcile ServiceMonitor")
		return ctrl.Result{}, err
	}

	// 10. Update status
	if err := r.updateStatus(ctx, engine); err != nil {
		log.Error(err, "Failed to update status")
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *LMCacheEngineReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&lmcachev1alpha1.LMCacheEngine{}).
		Owns(&appsv1.DaemonSet{}).
		Owns(&corev1.ConfigMap{}).
		Owns(&corev1.Service{}).
		Named("lmcacheengine").
		Complete(r)
}

// Ensure resources package is used (for linter).
var _ = resources.StandardLabels
