// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package tls

import (
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/projectcapsule/capsule/pkg/runtime/cert"
	"github.com/projectcapsule/capsule/pkg/runtime/configuration"
)

type ManagedCRD struct {
	Name                     string
	ManageConversion         bool
	ConversionPath           string
	ConversionReviewVersions []string
}

func (r Reconciler) managedCRDs() map[string]ManagedCRD {
	return map[string]ManagedCRD{
		"tenants": {
			Name:                     r.Configuration.TenantCRDName(),
			ManageConversion:         true,
			ConversionPath:           "/convert",
			ConversionReviewVersions: []string{"v1", "v1beta1"},
		},
		"capsuleconfigurations": {
			Name:                     "capsuleconfigurations.capsule.clastix.io",
			ManageConversion:         true,
			ConversionPath:           "/convert",
			ConversionReviewVersions: []string{"v1", "v1beta1"},
		},
		"customquotas": {
			Name: "customquotas.capsule.clastix.io",
		},
		"globalcustomquotas": {
			Name: "globalcustomquotas.capsule.clastix.io",
		},
		"globalresourcequotas": {
			Name: "globalresourcequotas.capsule.clastix.io",
		},
		"globaltenantresources": {
			Name: "globaltenantresources.capsule.clastix.io",
		},
		"quantityledgers": {
			Name: "quantityledgers.capsule.clastix.io",
		},
		"resourcepoolclaims": {
			Name: "resourcepoolclaims.capsule.clastix.io",
		},
		"resourcepools": {
			Name: "resourcepools.capsule.clastix.io",
		},
		"rulestatuses": {
			Name: "rulestatuses.capsule.clastix.io",
		},
		"tenantowners": {
			Name: "tenantowners.capsule.clastix.io",
		},
		"tenantresources": {
			Name: "tenantresources.capsule.clastix.io",
		},
	}
}

func (r Reconciler) managedCRDNames() []string {
	crds := r.managedCRDs()

	names := make([]string, 0, len(crds))

	for _, crd := range crds {
		names = append(names, crd.Name)
	}

	return names
}

func (r Reconciler) conversionManagedCRDs() map[string]ManagedCRD {
	crds := r.managedCRDs()

	out := make(map[string]ManagedCRD, len(crds))

	for key, crd := range crds {
		if crd.ManageConversion {
			out[key] = crd
		}
	}

	return out
}

// Collects required SANs for certificate
// desiredWebhookSANs collects required SANs for the webhook serving certificate.
func (r *Reconciler) desiredWebhookSANs(ctx context.Context) (cert.CertificateSANs, error) {
	sans := cert.CertificateSANs{}

	sans.AddDNSNames(
		r.Configuration.Admission().ServiceName,
		fmt.Sprintf("%s.%s", r.Configuration.Admission().ServiceName, r.Namespace),
		fmt.Sprintf("%s.%s.svc", r.Configuration.Admission().ServiceName, r.Namespace),
		fmt.Sprintf("%s.%s.svc.cluster.local", r.Configuration.Admission().ServiceName, r.Namespace),
	)

	mutating := r.Configuration.Admission().Mutating.Client
	if mutating != nil {
		sans.AddServiceReference(mutating.Service, configuration.ControllerNamespace())

		if err := sans.AddURL(mutating.URL); err != nil {
			return cert.CertificateSANs{}, fmt.Errorf("mutating admission client URL: %w", err)
		}
	}

	validating := r.Configuration.Admission().Validating.Client
	if validating != nil {
		sans.AddServiceReference(validating.Service, configuration.ControllerNamespace())

		if err := sans.AddURL(validating.URL); err != nil {
			return cert.CertificateSANs{}, fmt.Errorf("validating admission client URL: %w", err)
		}
	}

	sans = sans.Normalize()

	if sans.Empty() {
		return cert.CertificateSANs{}, fmt.Errorf("no webhook SANs could be resolved")
	}

	r.Log.V(5).Info(
		"Evaluated required SANs for TLS controller",
		"dnsNames", sans.DNSNames,
		"ipAddresses", cert.IPsToStrings(sans.IPAddrs),
	)

	return sans, nil
}

func FetchCurrentCaBundleForAdmission(
	ctx context.Context,
	c client.Reader,
	cfg configuration.Configuration,
) ([]byte, error) {
	// Internal Capsule TLS enabled: source of truth is the TLS Secret.
	if cfg.EnableTLSConfiguration() {
		secret := &corev1.Secret{}

		if err := c.Get(ctx, types.NamespacedName{
			Namespace: configuration.ControllerNamespace(),
			Name:      cfg.TLSSecretName(),
		}, secret); err != nil {
			return nil, fmt.Errorf("get TLS Secret %s/%s: %w",
				configuration.ControllerNamespace(),
				cfg.TLSSecretName(),
				err,
			)
		}

		caBundle := secret.Data[corev1.ServiceAccountRootCAKey]
		if len(caBundle) == 0 {
			return nil, fmt.Errorf("TLS Secret %s/%s missing %q",
				secret.Namespace,
				secret.Name,
				corev1.ServiceAccountRootCAKey,
			)
		}

		return append([]byte(nil), caBundle...), nil
	}

	// TLS Controller not enabled
	return nil, nil
}
