package vmscaleset

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	proxmoxv1alpha1 "github.com/sankofa/crossplane-provider-proxmox/apis/v1alpha1"
	"github.com/sankofa/crossplane-provider-proxmox/pkg/metrics"
	"github.com/sankofa/crossplane-provider-proxmox/pkg/proxmox"
	"github.com/sankofa/crossplane-provider-proxmox/pkg/scaling"
)

// ProxmoxVMScaleSetReconciler reconciles a ProxmoxVMScaleSet object
type ProxmoxVMScaleSetReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=proxmox.sankofa.nexus,resources=proxmoxvmscalesets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=proxmox.sankofa.nexus,resources=proxmoxvmscalesets/status,verbs=get;update;patch

// Reconcile is part of the main kubernetes reconciliation loop
func (r *ProxmoxVMScaleSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)

	var vmss proxmoxv1alpha1.ProxmoxVMScaleSet
	if err := r.Get(ctx, req.NamespacedName, &vmss); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	// Validate ProviderConfigReference
	if vmss.Spec.ProviderConfigReference == nil {
		return ctrl.Result{}, errors.New("providerConfigRef is required")
	}
	if vmss.Spec.ProviderConfigReference.Name == "" {
		return ctrl.Result{}, errors.New("providerConfigRef.name is required")
	}

	// Get ProviderConfig
	var providerConfig proxmoxv1alpha1.ProviderConfig
	if err := r.Get(ctx, client.ObjectKey{Name: vmss.Spec.ProviderConfigReference.Name}, &providerConfig); err != nil {
		return ctrl.Result{}, errors.Wrapf(err, "cannot get provider config")
	}

	// Get credentials from secret (similar to virtualmachine controller)
	creds, err := r.getCredentials(ctx, &providerConfig)
	if err != nil {
		logger.Error(err, "cannot get credentials")
		return ctrl.Result{RequeueAfter: 30 * time.Second}, errors.Wrap(err, "cannot get credentials")
	}

	// Find the site configuration (use first site or from spec if available)
	var site *proxmoxv1alpha1.ProxmoxSite
	if len(providerConfig.Spec.Sites) > 0 {
		site = &providerConfig.Spec.Sites[0]
	} else {
		return ctrl.Result{}, errors.New("no sites configured in provider config")
	}

	// Create Proxmox client with proper credentials
	proxmoxClient, err := proxmox.NewClient(
		site.Endpoint,
		creds.Username,
		creds.Password,
		site.InsecureSkipTLSVerify,
	)
	if err != nil {
		return ctrl.Result{}, errors.Wrap(err, "cannot create Proxmox client")
	}

	// Create metrics collector with Prometheus client
	// Get Prometheus endpoint from environment or ProviderConfig
	// For now, we'll use a default endpoint - in production this should come from config
	prometheusEndpoint := "http://prometheus:9090"
	if prometheusURL := os.Getenv("PROMETHEUS_ENDPOINT"); prometheusURL != "" {
		prometheusEndpoint = prometheusURL
	}
	prometheusClient := metrics.NewPrometheusAPIClient(prometheusEndpoint)
	metricsCollector := metrics.NewCollector(prometheusClient)

	// Create policy engine
	policyEngine := scaling.NewPolicyEngine(metricsCollector)

	// Create instance manager
	instanceManager := scaling.NewInstanceManager(proxmoxClient)

	// Evaluate scaling policies
	decision, err := policyEngine.Evaluate(ctx, vmss.Spec, vmss.Status, vmss.Status.Instances)
	if err != nil {
		logger.Error(err, "failed to evaluate scaling policies")
		return ctrl.Result{RequeueAfter: 30 * time.Second}, err
	}

	// Check cooldown period
	if vmss.Status.LastScaleTime != nil {
		cooldownPeriod := time.Duration(vmss.Spec.CooldownPeriod) * time.Second
		timeSinceLastScale := time.Since(vmss.Status.LastScaleTime.Time)
		if timeSinceLastScale < cooldownPeriod && decision.Action != "NO_ACTION" {
			logger.Info("Cooldown period active, skipping scaling", "timeSinceLastScale", timeSinceLastScale)
			return ctrl.Result{RequeueAfter: cooldownPeriod - timeSinceLastScale}, nil
		}
	}

	// Execute scaling decision
	if decision.Action != "NO_ACTION" {
		logger.Info("Scaling decision", "action", decision.Action, "newReplicas", decision.NewReplicas, "reason", decision.Reason)

		// Scale instances
		newInstances, err := instanceManager.ScaleTo(
			ctx,
			vmss.Spec.Template,
			vmss.Status.Instances,
			decision.NewReplicas,
		)
		if err != nil {
			logger.Error(err, "failed to scale instances")
			return ctrl.Result{RequeueAfter: 30 * time.Second}, err
		}

		// Update status
		now := metav1.Now()
		vmss.Status.CurrentReplicas = len(newInstances)
		vmss.Status.DesiredReplicas = decision.NewReplicas
		vmss.Status.Instances = newInstances
		vmss.Status.LastScaleTime = &now

		// Add scaling event
		event := proxmoxv1alpha1.ScalingEvent{
			Type:        decision.Action,
			OldReplicas: len(vmss.Status.Instances),
			NewReplicas: decision.NewReplicas,
			Reason:      decision.Reason,
			Timestamp:   now,
		}
		vmss.Status.ScalingEvents = append(vmss.Status.ScalingEvents, event)

		// Keep only last 10 events
		if len(vmss.Status.ScalingEvents) > 10 {
			vmss.Status.ScalingEvents = vmss.Status.ScalingEvents[len(vmss.Status.ScalingEvents)-10:]
		}

		// Count ready replicas
		readyCount := 0
		for _, instance := range newInstances {
			if instanceManager.HealthCheck(ctx, instance) {
				readyCount++
			}
		}
		vmss.Status.ReadyReplicas = readyCount

		if err := r.Status().Update(ctx, &vmss); err != nil {
			logger.Error(err, "failed to update status")
			return ctrl.Result{}, err
		}
	}

	// Requeue for periodic evaluation
	return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

// getCredentials retrieves credentials from the provider config secret
func (r *ProxmoxVMScaleSetReconciler) getCredentials(ctx context.Context, config *proxmoxv1alpha1.ProviderConfig) (*credentials, error) {
	if config.Spec.Credentials.SecretRef == nil {
		return nil, fmt.Errorf("no secret reference in provider config")
	}

	secretRef := config.Spec.Credentials.SecretRef
	
	// Get secret from Kubernetes
	secret := &corev1.Secret{}
	secretKey := client.ObjectKey{
		Namespace: secretRef.Namespace,
		Name:      secretRef.Name,
	}
	
	if err := r.Get(ctx, secretKey, secret); err != nil {
		return nil, errors.Wrap(err, "cannot get secret")
	}
	
	// Parse credentials from secret
	var username, password string
	
	// Try username/password format first
	if userData, ok := secret.Data["username"]; ok {
		username = string(userData)
	}
	if passData, ok := secret.Data["password"]; ok {
		password = string(passData)
	}
	
	// Try token format (for Proxmox API tokens)
	if tokenData, ok := secret.Data["token"]; ok {
		if userData, ok := secret.Data["tokenid"]; ok {
			username = string(userData)
		}
		password = string(tokenData)
	}
	
	if username == "" || password == "" {
		return nil, fmt.Errorf("username/password or token missing in secret")
	}
	
	return &credentials{
		Username: username,
		Password: password,
	}, nil
}

type credentials struct {
	Username string
	Password string
}

// SetupWithManager sets up the controller with the Manager.
func (r *ProxmoxVMScaleSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&proxmoxv1alpha1.ProxmoxVMScaleSet{}).
		Complete(r)
}

