Newer
Older
EnvoyControlPlane / internal / api_handlers.go
package internal

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	internalapi "envoy-control-plane/internal/api"
	"envoy-control-plane/internal/pkg/cert/tool"
	"envoy-control-plane/internal/pkg/snapshot"
	"envoy-control-plane/internal/pkg/storage"

	_ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
	resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/reflect/protoreflect"

	internalcert "envoy-control-plane/internal/pkg/cert"
)

// ---------------- Persistence Handlers ----------------

// loadSnapshotFromDB loads the full configuration from the persistent database
// into the SnapshotManager's Envoy Cache.
func (api *API) loadSnapshotFromDB(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	// Use context.Background() since this is a top-level operation
	if err := api.Manager.LoadSnapshotFromDB(context.Background()); err != nil {
		http.Error(w, fmt.Sprintf("failed to load from DB: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": "Configuration loaded from DB and applied to cache."})
}

// flushCacheToDB saves the current configuration from the Envoy Cache (source of truth)
// to the persistent database.
func (api *API) flushCacheToDB(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	// Default to DeleteLogical (no physical deletion)
	deleteStrategy := storage.DeleteLogical // DeleteLogical is assumed to be defined elsewhere

	// Check for 'deleteMissing' query parameter. If "true" or "1", switch to DeleteActual.
	deleteMissingStr := r.URL.Query().Get("deleteMissing")
	if deleteMissingStr == "true" || deleteMissingStr == "1" {
		deleteStrategy = storage.DeleteActual // DeleteActual is assumed to be defined elsewhere
	}

	// Use context.Background() since this is a top-level operation
	// Pass the determined DeleteStrategy
	if err := api.Manager.FlushCacheToDB(context.Background(), deleteStrategy); err != nil {
		http.Error(w, fmt.Sprintf("failed to flush to DB: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": "Configuration saved from cache to DB."})
}

// loadSnapshotFromFile loads a snapshot from a local file and applies it to the cache.
func (api *API) loadSnapshotFromFile(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	var req internalapi.SnapshotFileRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Path == "" {
		http.Error(w, "path required in request body", http.StatusBadRequest)
		return
	}

	resources, err := snapshot.LoadSnapshotFromFile(context.Background(), req.Path)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to load snapshot from file: %v", err), http.StatusInternalServerError)
		return
	}

	// Use context.Background()
	if err := api.Manager.SetSnapshot(context.Background(), req.Path, resources); err != nil {
		http.Error(w, fmt.Sprintf("failed to set snapshot: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": fmt.Sprintf("Snapshot loaded from %s and applied.", req.Path)})
}

// saveSnapshotToFile saves the current cache snapshot to a local file.
func (api *API) saveSnapshotToFile(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	var req internalapi.SnapshotFileRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Path == "" {
		http.Error(w, "path required in request body", http.StatusBadRequest)
		return
	}
	snap, err := api.Manager.Cache.GetSnapshot(api.Manager.NodeID)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to get snapshot: %v", err), http.StatusInternalServerError)
		return
	}
	if err := snapshot.SaveSnapshotToFile(snap, req.Path); err != nil {
		http.Error(w, fmt.Sprintf("failed to save snapshot to file: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": fmt.Sprintf("Snapshot saved to %s.", req.Path)})
}

// ---------------- Generic REST Handlers ----------------

// ---------------- Generic REST Handlers ----------------

// addResourceHandler handles adding a resource (Cluster, Listener) to the cache and persisting it.
func (api *API) addResourcesHandler(w http.ResponseWriter, r *http.Request, typ resourcev3.Type, createFn func(interface{}) ([]types.Resource, bool)) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	var req interface{}
	switch typ {
	case resourcev3.ClusterType:
		req = &internalapi.AddClusterRequest{}
	// case resourcev3.RouteType:
	//  req = &AddRouteRequest{}
	case resourcev3.ListenerType:
		req = &internalapi.AddListenerRequest{}
	case resourcev3.SecretType:
		req = &internalapi.AddSecretRequest{}
	// case resourcev3.EndpointType:
	default:
		http.Error(w, "unsupported type", http.StatusBadRequest)
		return
	}

	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	resources, upsert := createFn(req)
	// Check if any resources were created
	if len(resources) == 0 {
		http.Error(w, "create function returned no resources", http.StatusInternalServerError)
		return
	}

	// --- FIX: Initialize array to store names ---
	addedNames := make([]string, 0, len(resources))

	for _, r := range resources {
		if err := api.Manager.AddResourceToSnapshot(r, typ, upsert); err != nil {
			http.Error(w, fmt.Sprintf("failed to add resource: %v", err), http.StatusInternalServerError)
			return
		}

		// --- FIX: Collect name during iteration ---
		if nameable, ok := r.(interface{ GetName() string }); ok {
			addedNames = append(addedNames, nameable.GetName())
		}
	}

	// Persist immediately using DeleteLogical (mark as disabled in DB)
	if err := api.Manager.FlushCacheToDB(context.Background(), storage.DeleteLogical); err != nil {
		http.Error(w, fmt.Sprintf("failed to persist resource to DB: %v", err), http.StatusInternalServerError)
		return
	}

	// --- FIX: Encode the array of names in the response ---
	w.WriteHeader(http.StatusCreated)
	response := map[string]interface{}{
		"status": "created",
		"names":  addedNames,
	}

	// Fallback if no names were collected (e.g., resource type doesn't implement GetName())
	if len(addedNames) == 0 {
		response["names"] = fmt.Sprintf("failed to collect names for %d resources", len(resources))
	}

	json.NewEncoder(w).Encode(response)
}

// disableResourceHandler handles disabling a resource (logical removal from cache/DB).
func (api *API) disableResourceHandler(w http.ResponseWriter, r *http.Request, typ resourcev3.Type) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	var req struct{ Name string }
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
		http.Error(w, "name required", http.StatusBadRequest)
		return
	}

	// Use DeleteLogical to remove from cache and mark as disabled in DB
	if err := api.Manager.RemoveResource(req.Name, typ, storage.DeleteLogical); err != nil {
		http.Error(w, fmt.Sprintf("failed to disable resource: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": fmt.Sprintf("Resource '%s' disabled.", req.Name)})
}

// enableResourceHandler fetches a disabled resource from the DB and enables it (adds to cache).
func (api *API) enableResourceHandler(w http.ResponseWriter, r *http.Request, typ resourcev3.Type) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	var req internalapi.EnableResourceRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
		http.Error(w, "name required", http.StatusBadRequest)
		return
	}

	// Call the Manager function to pull the resource from DB, enable it, and add to the cache.
	if err := api.Manager.EnableResourceFromDB(req.Name, typ); err != nil {
		// NOTE: Exact error string check is brittle but necessary to replicate original logic's status code.
		if err.Error() == fmt.Sprintf("disabled resource %s not found in DB for type %s", req.Name, typ) {
			http.Error(w, fmt.Sprintf("disabled resource '%s' not found or already enabled: %v", req.Name, err), http.StatusNotFound)
		} else {
			http.Error(w, fmt.Sprintf("failed to enable resource '%s' from DB: %v", req.Name, err), http.StatusInternalServerError)
		}
		return
	}

	// Reload the cache again from DB to ensure consistency.
	if err := api.Manager.LoadSnapshotFromDB(context.Background()); err != nil {
		http.Error(w, fmt.Sprintf("failed to reload snapshot from DB after enabling resource: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": fmt.Sprintf("Resource '%s' enabled and applied to cache.", req.Name)})
}

// removeResourceHandler removes a resource completely from the DB (DeleteActual).
// It requires the resource to be disabled (not in the cache) before actual deletion.
func (api *API) removeResourceHandler(w http.ResponseWriter, r *http.Request, typ resourcev3.Type) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	var req internalapi.RemoveResourceRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
		http.Error(w, "name required", http.StatusBadRequest)
		return
	}

	// Use DeleteActual strategy for permanent removal from the DB.
	if err := api.Manager.RemoveResource(req.Name, typ, storage.DeleteActual); err != nil {
		// NOTE: Exact error string check is brittle but necessary to replicate original logic's status code.
		if err.Error() == fmt.Sprintf("resource %s for type %s is enabled and must be disabled before removal", req.Name, typ) {
			http.Error(w, fmt.Sprintf("resource '%s' must be disabled first before permanent removal: %v", req.Name, err), http.StatusBadRequest)
		} else {
			http.Error(w, fmt.Sprintf("failed to permanently remove resource: %v", err), http.StatusInternalServerError)
		}
		return
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ok", "message": fmt.Sprintf("Resource '%s' permanently removed.", req.Name)})
}

// appendFilterChainHandler defines the append filter handler.
func (api *API) appendFilterChainHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	var req internalapi.AppendFilterChainRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.ListenerName == "" || req.YAML == "" {
		http.Error(w, "listener name and YAML required", http.StatusBadRequest)
		return
	}
	ctx := context.Background()
	chain, err := snapshot.LoadFilterChainFromYAML(ctx, req.YAML)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to load filter chain %v", err), http.StatusBadRequest)
		return
	}
	if err := api.Manager.AppendFilterChainToListener(ctx, req.ListenerName, chain, req.Upsert); err != nil {
		http.Error(w, fmt.Sprintf("failed to append filter chain: %v", err), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func (api *API) updateFilterChainHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	var req internalapi.UpdateFilterChainRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.ListenerName == "" || req.YAML == "" {
		http.Error(w, "listener name and YAML required", http.StatusBadRequest)
		return
	}
	ctx := context.Background()
	chain, err := snapshot.LoadFilterChainFromYAML(ctx, req.YAML)
	if err != nil {
		http.Error(w, "failed to load filter chain", http.StatusBadRequest)
		return
	}
	if err := api.Manager.UpdateFilterChainOfListener(ctx, req.ListenerName, chain); err != nil {
		http.Error(w, fmt.Sprintf("failed to update filter chain: %v", err), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func (api *API) removeFilterChainHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	var req internalapi.RemoveFilterChainRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.ListenerName == "" {
		http.Error(w, "listener name required", http.StatusBadRequest)
		return
	}
	ctx := context.Background()
	if err := api.Manager.RemoveFilterChainFromListener(ctx, req.ListenerName, req.Domains); err != nil {
		http.Error(w, fmt.Sprintf("failed to remove filter chain: %v", err), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

// ---------------- Query / List Handlers ----------------

// listResourceHandler returns a list of enabled and disabled resources of a given type.
func (api *API) listResourceHandler(w http.ResponseWriter, r *http.Request, typ resourcev3.Type) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	enabledResources, disabledResources, err := api.Manager.ListResources(typ)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to list resources: %v", err), http.StatusInternalServerError)
		return
	}

	// Create the final response object structure
	response := struct {
		Enabled  []types.Resource `json:"enabled"`
		Disabled []types.Resource `json:"disabled"`
	}{
		Enabled:  enabledResources,
		Disabled: disabledResources,
	}

	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(response); err != nil {
		http.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

// getResourceHandler returns a single resource by name, allowing for different output formats.
func (api *API) getResourceHandler(w http.ResponseWriter, r *http.Request, typ resourcev3.Type) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	name := r.URL.Query().Get("name")
	if name == "" {
		http.Error(w, "name query parameter required", http.StatusBadRequest)
		return
	}

	format := r.URL.Query().Get("format")
	if format == "" {
		format = "json" // default format
	}

	res, err := api.Manager.GetResourceFromCache(name, typ)
	if err != nil {
		http.Error(w, fmt.Sprintf("resource not found: %v", err), http.StatusNotFound)
		return
	}

	pb, ok := res.(interface{ ProtoReflect() protoreflect.Message })
	if !ok {
		http.Error(w, "resource is not a protobuf message", http.StatusInternalServerError)
		return
	}

	var output []byte
	switch format {
	case "yaml":
		// ConvertProtoToYAML is assumed to be defined elsewhere
		yamlStr, err := ConvertProtoToYAML(pb)
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to convert resource to YAML: %v", err), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/x-yaml")
		output = []byte(yamlStr)
	default: // json
		data, err := protojson.Marshal(pb)
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to marshal protobuf to JSON: %v", err), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		output = data
	}

	w.WriteHeader(http.StatusOK)
	w.Write(output)
}

// ---------------- Consistency Handler ----------------

// isConsistentHandler checks whether the current in-memory cache is consistent with the database.
func (api *API) isConsistentHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	// ConsistencyReport is assumed to be defined elsewhere
	consistent, err := api.Manager.CheckCacheDBConsistency(context.TODO())
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to check consistency: %v", err), http.StatusInternalServerError)
		return
	}

	response := struct {
		Consistent *internalapi.ConsistencyReport `json:"consistent"` // ConsistencyReport is assumed to be defined elsewhere
	}{
		Consistent: consistent,
	}

	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(response); err != nil {
		http.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

func (api *API) issueCertificateHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	if !api.enableCertIssuance {
		http.Error(w, "certificate issuance is not enabled", http.StatusForbidden)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	var req internalapi.RequestDomainCertificate
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Domain == "" || req.Email == "" || req.Issuer == "" {
		http.Error(w, "domain, email, and issuer required", http.StatusBadRequest)
		return
	}

	issuer, err := internalcert.NewCertIssuer(req.Issuer)
	if err != nil {
		http.Error(w, "failed to create certificate issuer", http.StatusInternalServerError)
		return
	}
	cert, err := issuer.IssueCertificate(req.Domain, api.acmeWebRootPath, req.Email)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to issue certificate: %v", err), http.StatusInternalServerError)
		return
	}
	// Persist certificate data if SecretName is provided, this means the user is going to use it for envoy Secret resource.
	if err := internalcert.SaveCertificateData(context.TODO(), api.Manager.DB, cert, req.Email, req.Issuer, req.SecretName); err != nil {
		http.Error(w, fmt.Sprintf("failed to persist certificate data: %v", err), http.StatusInternalServerError)
		return
	}
	if req.SecretName != "" {
		if err := api.Manager.UpdateSDSSecretByName(r.Context(), req.SecretName, cert); err != nil {
			http.Error(w, fmt.Sprintf("failed to update SDS Secret in cache: %v", err), http.StatusInternalServerError)
			return
		}
	} else {
		if err := api.Manager.AddSDSSecretWithCert(r.Context(), cert); err != nil {
			http.Error(w, fmt.Sprintf("failed to add SDS Secret in cache: %v", err), http.StatusInternalServerError)
			return
		}
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(cert)
	w.WriteHeader(http.StatusOK)

}

func (api *API) renewCertificateHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	if !api.enableCertIssuance {
		http.Error(w, "certificate issuance is not enabled", http.StatusForbidden)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	var req internalapi.RenewCertificateRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Domain == "" {
		http.Error(w, "domain required", http.StatusBadRequest)
		return
	}
	oldCert, email, issuertype, err := internalcert.LoadCertificateData(context.Background(), api.Manager.DB, req.Domain)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to load existing certificate data: %v", err), http.StatusInternalServerError)
		return
	}

	issuer, err := internalcert.NewCertIssuer(issuertype)
	if err != nil {
		http.Error(w, "failed to create certificate issuer", http.StatusInternalServerError)
		return
	}

	newCert, err := issuer.RenewCertificate(oldCert, api.acmeWebRootPath, email)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to renew certificate: %v", err), http.StatusInternalServerError)
		return
	}
	if err := internalcert.SaveCertificateData(context.TODO(), api.Manager.DB, newCert, email, issuertype, req.SecretName); err != nil {
		http.Error(w, fmt.Sprintf("failed to persist renewed certificate data: %v", err), http.StatusInternalServerError)
		return
	}
	if req.SecretName != "" {
		if err := api.Manager.UpdateSDSSecretByName(r.Context(), req.SecretName, newCert); err != nil {
			http.Error(w, fmt.Sprintf("failed to update SDS Secret in cache: %v", err), http.StatusInternalServerError)
			return
		}
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(newCert)
}

func (api *API) parseCertificateHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	var req internalapi.ParseCertificateRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.CertificatePEM == "" {
		http.Error(w, "domain, email, and issuer required", http.StatusBadRequest)
		return
	}

	var parser tool.CertificateParser
	cert_infos, err := parser.Parse([]byte(req.CertificatePEM))
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to parse certificate: %v", err), http.StatusBadRequest)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(cert_infos)
	w.WriteHeader(http.StatusOK)
}

func (api *API) checkCertificateValidityHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	var req internalapi.CheckCertificateValidityRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, fmt.Sprintf("invalid request with erro %v", err), http.StatusBadRequest)
		return
	}
	if req.CertificatePEM == "" {
		http.Error(w, "certificate_pem required", http.StatusBadRequest)
		return
	}

	// Use the CertificateParser to check validity

	var parser tool.CertificateParser
	valid, err := parser.IsValid([]byte(req.CertificatePEM))
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to check certificate validity: %v", err), http.StatusBadRequest)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]bool{"valid": valid})
	w.WriteHeader(http.StatusOK)
}

func (api *API) getCertificateHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	domain := r.URL.Query().Get("domain")
	secretName := r.URL.Query().Get("secret_name")

	if domain == "" && secretName == "" {
		http.Error(w, "either domain or secret_name query parameter required", http.StatusBadRequest)
		return
	}
	if domain != "" && secretName != "" {
		http.Error(w, "only one of domain or secret_name query parameter should be provided", http.StatusBadRequest)
		return
	}

	var cert *storage.CertStorage
	var err error
	if domain != "" {
		cert, err = api.Manager.DB.LoadCertificate(context.Background(), domain)
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to load certificate for domain %s: %v", domain, err), http.StatusInternalServerError)
			return
		}
		if cert == nil {
			http.Error(w, fmt.Sprintf("no certificate found for domain %s", domain), http.StatusNotFound)
			return
		}
	}
	if secretName != "" {
		cert, err = api.Manager.DB.LoadCertificateBySecretName(context.Background(), secretName)
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to load certificate for secret name %s: %v", secretName, err), http.StatusInternalServerError)
			return
		}
		if cert == nil {
			http.Error(w, fmt.Sprintf("no certificate found for secret name %s", secretName), http.StatusNotFound)
			return
		}
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(cert)
	w.WriteHeader(http.StatusOK)
}

func (api *API) storageDumpHandler(w http.ResponseWriter, r *http.Request) {
	ctx := context.Background()

	switch r.Method {
	case http.MethodGet:
		// --- 1. DUMP (Download) ---

		// Execute the database dump
		data, err := api.Manager.DB.Dump(ctx)
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to perform database dump: %v", err), http.StatusInternalServerError)
			return
		}

		// Set response headers for file download
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("Content-Disposition", "attachment; filename=\"db_dump.json\"")
		w.WriteHeader(http.StatusOK)
		w.Write(data)

	case http.MethodPost:
		// --- 2. RESTORE (Upload) ---

		// Determine the restore mode from query parameters
		modeStr := r.URL.Query().Get("mode")
		mode := storage.RestoreMerge // Default to Merge
		if modeStr == "override" {
			mode = storage.RestoreOverride
		}

		// Read the JSON dump content from the request body
		var data []byte
		var err error

		// Limit the body size to prevent resource exhaustion
		r.Body = http.MaxBytesReader(w, r.Body, 10*1024*1024) // 10MB limit

		data, err = io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "failed to read request body or body too large", http.StatusBadRequest)
			return
		}

		// Execute the database restore
		if err := api.Manager.DB.Restore(ctx, data, mode); err != nil {
			http.Error(w, fmt.Sprintf("failed to restore database: %v", err), http.StatusInternalServerError)
			return
		}

		// Reload the Envoy Cache from the newly restored database state
		if err := api.Manager.LoadSnapshotFromDB(ctx); err != nil {
			http.Error(w, fmt.Sprintf("database restored, but failed to load new snapshot into cache: %v", err), http.StatusInternalServerError)
			return
		}

		// Send success response
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(map[string]string{
			"status":  "ok",
			"message": fmt.Sprintf("Database restored in %s mode and cache updated.", modeStr),
		})

	default:
		// Handle unsupported methods
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
}

// rotateCertificatesHandler triggers the certificate rotation process for all certificates that are due for renewal.
func (api *API) enableCertificateRotationHandler(w http.ResponseWriter, r *http.Request) {
	ctx := context.Background()
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	var req internalapi.EnableCertificateRotationRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, fmt.Sprintf("invalid request with erro %v", err), http.StatusBadRequest)
		return
	}
	if req.Domain == "" {
		http.Error(w, "domain required", http.StatusBadRequest)
		return
	}

	if req.SecretName == "" {
		http.Error(w, "secret_name required", http.StatusBadRequest)
		return
	}

	// *If the req.RenewBefore string is not parsable, return an error response indicating the invalid format.*
	renewBefore := time.Duration(0) // Default to 0 if not provided.
	if req.RenewBefore != "" {
		var err error
		if renewBefore, err = time.ParseDuration(req.RenewBefore); err != nil {
			http.Error(w, fmt.Sprintf("invalid renew_before format: %v", err), http.StatusBadRequest)
			return
		}
	}
	certStorage, err := api.Manager.DB.LoadCertificate(ctx, req.Domain)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to load certificate for domain %s: %v", req.Domain, err), http.StatusInternalServerError)
		return
	}

	if certStorage == nil {
		http.Error(w, fmt.Sprintf("no certificate found for domain %s", req.Domain), http.StatusNotFound)
		return
	}
	if certStorage.SecretName != req.SecretName {
		http.Error(w, fmt.Sprintf("secret name mismatch for domain %s: expected %s, got %s", req.Domain, certStorage.SecretName, req.SecretName), http.StatusBadRequest)
		return
	}
	certStorage.RenewBefore = renewBefore
	certStorage.EnableRotation = true

	if err := api.Manager.DB.UpdateCertRotationSettings(ctx, certStorage); err != nil {
		http.Error(w, fmt.Sprintf("failed to update certificate rotation settings for domain %s: %v", req.Domain, err), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{
		"status":  "ok",
		"message": fmt.Sprintf("Certificate rotation enabled for domain %s with renew_before %s", req.Domain, req.RenewBefore),
	})
}

func (api *API) disableCertificateRotationHandler(w http.ResponseWriter, r *http.Request) {
	ctx := context.Background()
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	var req internalapi.DisableCertificateRotationRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, fmt.Sprintf("invalid request with error %v", err), http.StatusBadRequest)
		return
	}
	if req.Domain == "" {
		http.Error(w, "domain required", http.StatusBadRequest)
		return
	}

	certStorage, err := api.Manager.DB.LoadCertificate(ctx, req.Domain)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to load certificate for domain %s: %v", req.Domain, err), http.StatusInternalServerError)
		return
	}

	if certStorage == nil {
		http.Error(w, fmt.Sprintf("no certificate found for domain %s", req.Domain), http.StatusNotFound)
		return
	}

	certStorage.EnableRotation = false
	certStorage.RenewBefore = 0

	if err := api.Manager.DB.UpdateCertRotationSettings(ctx, certStorage); err != nil {
		http.Error(w, fmt.Sprintf("failed to update certificate rotation settings for domain %s: %v", req.Domain, err), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{
		"status":  "ok",
		"message": fmt.Sprintf("Certificate rotation disabled for domain %s", req.Domain),
	})
}

func (api *API) listRotatingCertificatesHandler(w http.ResponseWriter, r *http.Request) {
	ctx := context.Background()
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	certs, err := api.Manager.DB.LoadAllCertificates(ctx)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to list rotating certificates: %v", err), http.StatusInternalServerError)
		return
	}
	rotatingCerts := make([]*internalapi.RotatingCertificateInfo, 0)
	for _, cert := range certs {
		if cert.EnableRotation {
			rotatingCerts = append(rotatingCerts, &internalapi.RotatingCertificateInfo{
				Domain:          cert.Domain,
				SecretName:      cert.SecretName,
				RenewBefore:     cert.RenewBefore.String(),
				RotationEnabled: true,
			})
		}
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(rotatingCerts)
}