package controller

import (
	"context"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/intstr"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	momv1 "github.com/yourusername/container-mom-go/pkg/apis/containermom/v1alpha1"
	"github.com/yourusername/container-mom-go/pkg/domain"
)

type DeploymentReconciler struct {
	client.Client
	Scheme        *runtime.Scheme
	DomainManager *domain.Manager
}

func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)

	// Fetch the ContainerMomDeployment instance
	deployment := &momv1.ContainerMomDeployment{}
	err := r.Get(ctx, req.NamespacedName, deployment)
	if err != nil {
		if errors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	// Check billing status
	customer := &momv1.Customer{}
	err = r.Get(ctx, client.ObjectKey{
		Namespace: deployment.Namespace,
		Name:      deployment.Spec.BillingTier,
	}, customer)
	if err != nil {
		logger.Error(err, "failed to get customer")
		return ctrl.Result{}, err
	}

	if customer.Status.BillingStatus != "active" {
		logger.Info("customer billing inactive", "customer", customer.Name)
		return ctrl.Result{}, nil
	}

	// Handle source
	if deployment.Spec.Source.GitURL != "" {
		if err := r.handleSourceBuild(ctx, deployment); err != nil {
			logger.Error(err, "failed to handle source build")
			return ctrl.Result{}, err
		}
	}

	// Deploy to target clusters
	for _, cluster := range deployment.Spec.Clusters {
		if err := r.deployToCluster(ctx, deployment, cluster); err != nil {
			logger.Error(err, "failed to deploy to cluster", "cluster", cluster)
			continue
		}
	}

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

	return ctrl.Result{}, nil
}

func (r *DeploymentReconciler) handleSourceBuild(ctx context.Context, deployment *momv1.ContainerMomDeployment) error {
	// TODO: Implement source-to-image build
	// 1. Clone repository
	// 2. Build image using specified builder
	// 3. Push to registry
	return nil
}

func (r *DeploymentReconciler) deployToCluster(ctx context.Context, deployment *momv1.ContainerMomDeployment, cluster string) error {
	// Create Kubernetes Deployment
	k8sDeployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deployment.Name,
			Namespace: deployment.Namespace,
			Labels: map[string]string{
				"app.kubernetes.io/managed-by": "container-mom",
				"containermom.io/deployment":   deployment.Name,
			},
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: int32Ptr(1),
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": deployment.Name,
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": deployment.Name,
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:      deployment.Name,
							Image:     deployment.Spec.Source.Image,
							Env:       convertEnvVars(deployment.Spec.Env),
							Resources: corev1.ResourceRequirements{
								// Convert and set resource requirements
							},
						},
					},
				},
			},
		},
	}

	// Create or update the deployment
	if err := r.Create(ctx, k8sDeployment); err != nil {
		if errors.IsAlreadyExists(err) {
			return r.Update(ctx, k8sDeployment)
		}
		return err
	}

	// Create Service
	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deployment.Name,
			Namespace: deployment.Namespace,
		},
		Spec: corev1.ServiceSpec{
			Selector: map[string]string{
				"app": deployment.Name,
			},
			Ports: []corev1.ServicePort{
				{
					Port:       80,
					TargetPort: intstr.FromInt(8080),
				},
			},
		},
	}

	if err := r.Create(ctx, service); err != nil {
		if !errors.IsAlreadyExists(err) {
			return err
		}
	}

	// Handle domain/ingress setup
	if err := r.setupDomain(ctx, deployment); err != nil {
		return err
	}

	return nil
}

func (r *DeploymentReconciler) setupDomain(ctx context.Context, deployment *momv1.ContainerMomDeployment) error {
	return r.DomainManager.SetupDomain(ctx, deployment)
}

func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&momv1.ContainerMomDeployment{}).
		Owns(&appsv1.Deployment{}).
		Owns(&corev1.Service{}).
		Complete(r)
}

func int32Ptr(i int32) *int32 {
	return &i
}

func convertEnvVars(envVars []momv1.EnvVar) []corev1.EnvVar {
	k8sEnvVars := make([]corev1.EnvVar, len(envVars))
	for i, env := range envVars {
		k8sEnvVars[i] = corev1.EnvVar{
			Name:  env.Name,
			Value: env.Value,
		}
	}
	return k8sEnvVars
}
