// Package operator implements the core container-mom operator functionality
package operator

import (
	"context"
	"fmt"
	"time"

	v1alpha1 "github.com/pfeifferj/container-mom-go/pkg/apis/containermom/v1alpha1"
	"github.com/pfeifferj/container-mom-go/pkg/operator/mongodb"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/util/workqueue"
	ctrl "sigs.k8s.io/controller-runtime"
)

// DeploymentController manages the lifecycle of ContainerMomDeployments
// nolint:staticcheck // TODO: Migrate to newer workqueue API when available
type DeploymentController struct {
	mongodb     mongodb.Interface
	reconciler  *DeploymentReconciler
	queue       workqueue.RateLimitingInterface
	lastSyncMap map[string]time.Time // Track last sync time per deployment
}

// NewDeploymentController creates a new DeploymentController instance
// nolint:staticcheck // TODO: Migrate to newer workqueue API when available
func NewDeploymentController(mongodb mongodb.Interface, reconciler *DeploymentReconciler) *DeploymentController {
	return &DeploymentController{
		mongodb:     mongodb,
		reconciler:  reconciler,
		queue:       workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
		lastSyncMap: make(map[string]time.Time),
	}
}

// Start begins watching and reconciling deployments
func (c *DeploymentController) Start(ctx context.Context) error {
	ctrl.Log.Info("starting MongoDB watch")
	// Start the worker
	go c.worker(ctx)

	// Start periodic full sync
	go c.periodicFullSync(ctx)

	ctrl.Log.Info("deployment controller started successfully")
	<-ctx.Done()
	return nil
}

func (c *DeploymentController) worker(ctx context.Context) {
	for {
		obj, shutdown := c.queue.Get()
		if shutdown {
			return
		}

		func() {
			defer c.queue.Done(obj)
			deploymentID, ok := obj.(string)
			if !ok {
				ctrl.Log.Error(nil, "failed to process non-string deployment ID", "object", obj)
				c.queue.Forget(obj)
				return
			}

			if err := c.reconcileDeployment(ctx, deploymentID); err != nil {
				if c.queue.NumRequeues(obj) < 5 {
					c.queue.AddRateLimited(obj)
					return
				}
				c.queue.Forget(obj)
			}
		}()
	}
}

func (c *DeploymentController) periodicFullSync(ctx context.Context) {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			if err := c.fullSync(ctx); err != nil {
				ctrl.Log.Error(err, "failed to perform full sync")
			}
		}
	}
}

func (c *DeploymentController) fullSync(ctx context.Context) error {
	ctrl.Log.Info("performing full sync")
	// Get all deployments from MongoDB
	collection := c.mongodb.Database("containermom").Collection("deployments")
	cursor, err := collection.Find(ctx, bson.M{})
	if err != nil {
		ctrl.Log.Error(err, "failed to fetch deployments from MongoDB")
		return fmt.Errorf("failed to fetch deployments: %w", err)
	}
	defer func() {
		if err := cursor.Close(ctx); err != nil {
			ctrl.Log.Error(err, "failed to close cursor")
		}
	}()

	// Track deployments we've seen
	seenDeployments := make(map[string]bool)

	// Process each deployment
	for cursor.Next(ctx) {
		var deployment MongoDeployment
		if err := cursor.Decode(&deployment); err != nil {
			return fmt.Errorf("failed to decode deployment: %w", err)
		}

		seenDeployments[deployment.ID] = true
		c.queue.Add(deployment.ID)
	}

	// Get existing ContainerMomDeployments
	existingDeployments, err := c.reconciler.listExistingDeployments(ctx)
	if err != nil {
		return fmt.Errorf("failed to list existing deployments: %w", err)
	}

	// Only delete if we successfully got deployments from MongoDB
	for _, existing := range existingDeployments {
		if !seenDeployments[existing.Name] {
			// Double check the deployment doesn't exist in MongoDB before deleting
			count, err := collection.CountDocuments(ctx, bson.M{"_id": existing.Name})
			if err != nil {
				ctrl.Log.Error(err, "failed to verify deployment absence", "deployment", existing.Name)
				continue
			}
			if count == 0 {
				if err := c.reconciler.deleteDeployment(ctx, existing.Name); err != nil {
					ctrl.Log.Error(err, "failed to delete deployment", "deployment", existing.Name)
				}
			}
		}
	}

	return nil
}

func (c *DeploymentController) reconcileDeployment(ctx context.Context, deploymentID string) error {
	// Get deployment from MongoDB
	collection := c.mongodb.Database("containermom").Collection("deployments")
	var deployment MongoDeployment
	if err := collection.FindOne(ctx, bson.M{"_id": deploymentID}).Decode(&deployment); err != nil {
		if err == mongo.ErrNoDocuments {
			// Deployment was deleted, remove the CR
			return c.reconciler.deleteDeployment(ctx, deploymentID)
		}
		return fmt.Errorf("failed to fetch deployment: %w", err)
	}

	// Convert to ContainerMomDeployment
	cmDeployment := c.convertToK8sDeployment(&deployment)

	// Apply the deployment
	return c.reconciler.reconcileDeployment(ctx, cmDeployment)
}

func (c *DeploymentController) convertToK8sDeployment(deployment *MongoDeployment) *v1alpha1.ContainerMomDeployment {
	k8sDeployment := &v1alpha1.ContainerMomDeployment{
		ObjectMeta: metav1.ObjectMeta{
			Name: deployment.Name,
		},
		Spec: v1alpha1.ContainerMomDeploymentSpec{
			Source: v1alpha1.SourceSpec{
				GitURL: deployment.GitURL,
				Image:  deployment.ContainerImage,
			},
			Clusters:  deployment.Regions, // Using Regions as target clusters
			Domain:    deployment.Domain,  // Already using v1alpha1.DomainSpec
			Resources: deployment.Resources,
			Env:       deployment.Env,
		},
	}

	return k8sDeployment
}

// EnqueueDeployment adds a deployment to the reconciliation queue
func (c *DeploymentController) EnqueueDeployment(deploymentID string) {
	c.queue.Add(deploymentID)
}
