package kubernetes

import (
	"context"
	"fmt"
	"strings"

	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes"

	"github.com/orchard9/rdev/internal/port"
)

// Ensure PreviewManager implements port.PreviewManager at compile time.
var _ port.PreviewManager = (*PreviewManager)(nil)

// PreviewConfig holds configuration for the preview manager.
type PreviewConfig struct {
	// Namespace is the K8s namespace for preview resources.
	Namespace string

	// IngressClass is the ingress controller class (e.g., "traefik").
	IngressClass string

	// TLSIssuer is the cert-manager cluster issuer name.
	TLSIssuer string
}

// PreviewManager manages ephemeral preview URLs via K8s Service + Ingress.
type PreviewManager struct {
	client *kubernetes.Clientset
	config PreviewConfig
}

// NewPreviewManager creates a new K8s preview manager.
func NewPreviewManager(client *kubernetes.Clientset, cfg PreviewConfig) *PreviewManager {
	if cfg.IngressClass == "" {
		cfg.IngressClass = "traefik"
	}
	return &PreviewManager{
		client: client,
		config: cfg,
	}
}

// resourceName returns the K8s resource name for a session.
func resourceName(sessionID string) string {
	return "session-" + sessionID
}

// CreatePreview creates a K8s Service + Ingress for the session preview.
func (m *PreviewManager) CreatePreview(ctx context.Context, opts port.PreviewOptions) error {
	if opts.Port == 0 {
		opts.Port = 8080
	}

	name := resourceName(opts.SessionID)
	ns := opts.Namespace
	if ns == "" {
		ns = m.config.Namespace
	}

	// Create Service targeting the pod by its rdev project label.
	// Project pods are labeled with rdev.orchard9.ai/name=<project-id>.
	svc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
			Labels: map[string]string{
				"rdev.orchard9.ai/session": opts.SessionID,
				"rdev.orchard9.ai/preview": "true",
			},
		},
		Spec: corev1.ServiceSpec{
			// Use ExternalName-like routing via a selector that matches the pod.
			// Since project pods have rdev.orchard9.ai/name=<id>, we select by pod name
			// using the statefulset.kubernetes.io/pod-name label (set on StatefulSet pods).
			Selector: map[string]string{
				"statefulset.kubernetes.io/pod-name": opts.PodName,
			},
			Ports: []corev1.ServicePort{
				{
					Name:       "http",
					Port:       int32(opts.Port),
					TargetPort: intstr.FromInt32(int32(opts.Port)),
					Protocol:   corev1.ProtocolTCP,
				},
			},
			Type: corev1.ServiceTypeClusterIP,
		},
	}

	_, err := m.client.CoreV1().Services(ns).Create(ctx, svc, metav1.CreateOptions{})
	if err != nil && !errors.IsAlreadyExists(err) {
		return fmt.Errorf("create preview service: %w", err)
	}

	// Create Ingress for TLS-terminated route.
	pathType := networkingv1.PathTypePrefix
	tlsSecretName := strings.ReplaceAll(opts.Host, ".", "-") + "-tls"

	annotations := map[string]string{}
	if m.config.TLSIssuer != "" {
		annotations["cert-manager.io/cluster-issuer"] = m.config.TLSIssuer
	}

	ingress := &networkingv1.Ingress{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
			Labels: map[string]string{
				"rdev.orchard9.ai/session": opts.SessionID,
				"rdev.orchard9.ai/preview": "true",
			},
			Annotations: annotations,
		},
		Spec: networkingv1.IngressSpec{
			IngressClassName: &m.config.IngressClass,
			TLS: []networkingv1.IngressTLS{
				{
					Hosts:      []string{opts.Host},
					SecretName: tlsSecretName,
				},
			},
			Rules: []networkingv1.IngressRule{
				{
					Host: opts.Host,
					IngressRuleValue: networkingv1.IngressRuleValue{
						HTTP: &networkingv1.HTTPIngressRuleValue{
							Paths: []networkingv1.HTTPIngressPath{
								{
									Path:     "/",
									PathType: &pathType,
									Backend: networkingv1.IngressBackend{
										Service: &networkingv1.IngressServiceBackend{
											Name: name,
											Port: networkingv1.ServiceBackendPort{
												Number: int32(opts.Port),
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
	}

	_, err = m.client.NetworkingV1().Ingresses(ns).Create(ctx, ingress, metav1.CreateOptions{})
	if err != nil && !errors.IsAlreadyExists(err) {
		// Clean up the service if ingress creation fails.
		_ = m.client.CoreV1().Services(ns).Delete(ctx, name, metav1.DeleteOptions{})
		return fmt.Errorf("create preview ingress: %w", err)
	}

	return nil
}

// DeletePreview removes the K8s Service + Ingress for a session preview.
func (m *PreviewManager) DeletePreview(ctx context.Context, sessionID string) error {
	name := resourceName(sessionID)
	ns := m.config.Namespace

	// Delete Ingress (ignore not-found).
	err := m.client.NetworkingV1().Ingresses(ns).Delete(ctx, name, metav1.DeleteOptions{})
	if err != nil && !errors.IsNotFound(err) {
		return fmt.Errorf("delete preview ingress: %w", err)
	}

	// Delete Service (ignore not-found).
	err = m.client.CoreV1().Services(ns).Delete(ctx, name, metav1.DeleteOptions{})
	if err != nil && !errors.IsNotFound(err) {
		return fmt.Errorf("delete preview service: %w", err)
	}

	return nil
}
