package watchers

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"math/big"
	"os"
	"path/filepath"
	"testing"
	"time"

	cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
	core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
	tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
	resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
	"github.com/globulario/Globular/internal/controlplane"
	"github.com/globulario/Globular/internal/xds/builder"
	"github.com/globulario/Globular/internal/xds/secrets"
	"github.com/globulario/Globular/internal/xds/server"
	clientv3 "go.etcd.io/etcd/client/v3"
)

// Test certificate data (self-signed, generated at init for testing only)
var (
	testCert string
	testKey  string
	testCA   string
)

func init() {
	cert, key, ca := generateTestCertKey()
	testCert = string(cert)
	testKey = string(key)
	testCA = string(ca)
}

// TestSnapshotContainsSecrets verifies that snapshots include Secret resources when EnableSDS is true.
func TestSnapshotContainsSecrets(t *testing.T) {
	tmpDir := t.TempDir()

	// Create test certificate files
	certPath := filepath.Join(tmpDir, "cert.pem")
	keyPath := filepath.Join(tmpDir, "key.pem")
	caPath := filepath.Join(tmpDir, "ca.pem")

	if err := os.WriteFile(certPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(keyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key: %v", err)
	}
	if err := os.WriteFile(caPath, []byte(testCA), 0644); err != nil {
		t.Fatalf("write CA: %v", err)
	}

	// Build input with SDS enabled
	input := builder.Input{
		NodeID:    "test-node",
		EnableSDS: true,
		SDSSecrets: []builder.Secret{
			{
				Name:     "internal-server-cert",
				CertPath: certPath,
				KeyPath:  keyPath,
			},
			{
				Name:   "internal-ca-bundle",
				CAPath: caPath,
			},
		},
		Listener: builder.Listener{
			Name:      "test-listener",
			Port:      443,
			RouteName: "test-routes",
			CertFile:  certPath,
			KeyFile:   keyPath,
		},
		Routes: []builder.Route{
			{Prefix: "/", Cluster: "test-cluster"},
		},
		Clusters: []builder.Cluster{
			{
				Name:      "test-cluster",
				Endpoints: []builder.Endpoint{{Host: "localhost", Port: 8080}},
			},
		},
	}

	// Build snapshot
	snapshot, err := builder.BuildSnapshot(input, "v1")
	if err != nil {
		t.Fatalf("BuildSnapshot failed: %v", err)
	}

	// Verify snapshot contains Secret resources
	secrets := snapshot.GetResources(resource_v3.SecretType)
	if len(secrets) == 0 {
		t.Fatal("snapshot should contain Secret resources when EnableSDS=true")
	}

	if len(secrets) != 2 {
		t.Errorf("expected 2 secrets, got %d", len(secrets))
	}

	// Verify secret names
	secretNames := make(map[string]bool)
	for name := range secrets {
		secretNames[name] = true
	}

	if !secretNames["internal-server-cert"] {
		t.Error("snapshot missing internal-server-cert")
	}
	if !secretNames["internal-ca-bundle"] {
		t.Error("snapshot missing internal-ca-bundle")
	}

	t.Logf("✓ Snapshot contains %d secrets: %v", len(secrets), getSecretNames(secrets))
}

// TestSnapshotWithoutSDS verifies that snapshots don't include secrets when SDS is disabled.
func TestSnapshotWithoutSDS(t *testing.T) {
	tmpDir := t.TempDir()

	certPath := filepath.Join(tmpDir, "cert.pem")
	keyPath := filepath.Join(tmpDir, "key.pem")

	if err := os.WriteFile(certPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(keyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key: %v", err)
	}

	// Build input with SDS disabled (file-based TLS)
	input := builder.Input{
		NodeID:    "test-node",
		EnableSDS: false, // Explicitly disabled
		Listener: builder.Listener{
			Name:      "test-listener",
			Port:      443,
			RouteName: "test-routes",
			CertFile:  certPath,
			KeyFile:   keyPath,
		},
		Routes: []builder.Route{
			{Prefix: "/", Cluster: "test-cluster"},
		},
		Clusters: []builder.Cluster{
			{
				Name:      "test-cluster",
				Endpoints: []builder.Endpoint{{Host: "localhost", Port: 8080}},
			},
		},
	}

	// Build snapshot
	snapshot, err := builder.BuildSnapshot(input, "v1")
	if err != nil {
		t.Fatalf("BuildSnapshot failed: %v", err)
	}

	// Verify snapshot does NOT contain Secret resources
	secrets := snapshot.GetResources(resource_v3.SecretType)
	if len(secrets) != 0 {
		t.Errorf("snapshot should not contain secrets when EnableSDS=false, got %d", len(secrets))
	}

	t.Log("✓ Snapshot correctly excludes secrets when SDS disabled")
}

// TestListenerUsesSDS verifies that listeners have SDS secret configs when SDS is enabled.
func TestListenerUsesSDS(t *testing.T) {
	tmpDir := t.TempDir()

	certPath := filepath.Join(tmpDir, "cert.pem")
	keyPath := filepath.Join(tmpDir, "key.pem")

	if err := os.WriteFile(certPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(keyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key: %v", err)
	}

	// Build input with SDS enabled
	input := builder.Input{
		NodeID:    "test-node",
		EnableSDS: true,
		SDSSecrets: []builder.Secret{
			{
				Name:     "internal-server-cert",
				CertPath: certPath,
				KeyPath:  keyPath,
			},
		},
		Listener: builder.Listener{
			Name:      "test-listener",
			Port:      443,
			RouteName: "test-routes",
			CertFile:  certPath,
			KeyFile:   keyPath,
		},
		Routes: []builder.Route{
			{Prefix: "/", Cluster: "test-cluster"},
		},
		Clusters: []builder.Cluster{
			{
				Name:      "test-cluster",
				Endpoints: []builder.Endpoint{{Host: "localhost", Port: 8080}},
			},
		},
	}

	// Build snapshot
	snapshot, err := builder.BuildSnapshot(input, "v1")
	if err != nil {
		t.Fatalf("BuildSnapshot failed: %v", err)
	}

	// Get listener from snapshot
	listeners := snapshot.GetResources(resource_v3.ListenerType)
	if len(listeners) == 0 {
		t.Fatal("snapshot should contain listeners")
	}

	// Verify listener has SDS config
	for _, resource := range listeners {
		listener, ok := resource.(*listener_v3.Listener)
		if !ok {
			continue
		}

		// Check filter chains for TLS config
		for _, chain := range listener.FilterChains {
			if chain.TransportSocket == nil {
				continue
			}

			// Unmarshal the transport socket config
			var tlsContext tls_v3.DownstreamTlsContext
			if err := chain.TransportSocket.GetTypedConfig().UnmarshalTo(&tlsContext); err != nil {
				t.Logf("failed to unmarshal TLS context: %v", err)
				continue
			}

			// Verify SDS secret configs are present
			if tlsContext.CommonTlsContext == nil {
				t.Error("listener TLS context missing CommonTlsContext")
				continue
			}

			sdsConfigs := tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs
			if len(sdsConfigs) == 0 {
				t.Error("listener should have tls_certificate_sds_secret_configs when SDS enabled")
			} else {
				t.Logf("✓ Listener has SDS config: secret_name=%s", sdsConfigs[0].Name)

				// Verify secret name
				if sdsConfigs[0].Name != "internal-server-cert" {
					t.Errorf("expected secret name 'internal-server-cert', got '%s'", sdsConfigs[0].Name)
				}

				// Verify ADS config
				if sdsConfigs[0].SdsConfig == nil {
					t.Error("SDS config missing sds_config")
				} else if sdsConfigs[0].SdsConfig.GetAds() == nil {
					t.Error("SDS config should use ADS")
				}
			}
		}
	}
}

// TestClusterUsesSDS verifies that clusters have SDS validation context when SDS is enabled.
func TestClusterUsesSDS(t *testing.T) {
	tmpDir := t.TempDir()

	certPath := filepath.Join(tmpDir, "cert.pem")
	keyPath := filepath.Join(tmpDir, "key.pem")
	caPath := filepath.Join(tmpDir, "ca.pem")

	if err := os.WriteFile(certPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(keyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key: %v", err)
	}
	if err := os.WriteFile(caPath, []byte(testCA), 0644); err != nil {
		t.Fatalf("write CA: %v", err)
	}

	// Build input with SDS enabled and upstream TLS
	input := builder.Input{
		NodeID:    "test-node",
		EnableSDS: true,
		SDSSecrets: []builder.Secret{
			{
				Name:   "internal-ca-bundle",
				CAPath: caPath,
			},
		},
		Listener: builder.Listener{
			Name:      "test-listener",
			Port:      443,
			RouteName: "test-routes",
		},
		Routes: []builder.Route{
			{Prefix: "/", Cluster: "test-cluster"},
		},
		Clusters: []builder.Cluster{
			{
				Name:       "test-cluster",
				Endpoints:  []builder.Endpoint{{Host: "backend.example.com", Port: 8080}},
				ServerCert: certPath,
				KeyFile:    keyPath,
				CAFile:     caPath,
				SNI:        "backend.example.com",
			},
		},
	}

	// Build snapshot
	snapshot, err := builder.BuildSnapshot(input, "v1")
	if err != nil {
		t.Fatalf("BuildSnapshot failed: %v", err)
	}

	// Get clusters from snapshot
	clusters := snapshot.GetResources(resource_v3.ClusterType)
	if len(clusters) == 0 {
		t.Fatal("snapshot should contain clusters")
	}

	// Verify cluster has SDS validation context
	foundSDSCluster := false
	for _, resource := range clusters {
		cluster, ok := resource.(*cluster_v3.Cluster)
		if !ok {
			continue
		}

		if cluster.TransportSocket == nil {
			continue
		}

		// Unmarshal the transport socket config
		var tlsContext tls_v3.UpstreamTlsContext
		if err := cluster.TransportSocket.GetTypedConfig().UnmarshalTo(&tlsContext); err != nil {
			t.Logf("failed to unmarshal TLS context: %v", err)
			continue
		}

		if tlsContext.CommonTlsContext == nil {
			continue
		}

		// Check for SDS validation context
		sdsValidation := tlsContext.CommonTlsContext.GetValidationContextSdsSecretConfig()
		if sdsValidation != nil {
			foundSDSCluster = true
			t.Logf("✓ Cluster has SDS validation context: secret_name=%s", sdsValidation.Name)

			// Verify secret name
			if sdsValidation.Name != "internal-ca-bundle" {
				t.Errorf("expected secret name 'internal-ca-bundle', got '%s'", sdsValidation.Name)
			}

			// Verify ADS config
			if sdsValidation.SdsConfig == nil {
				t.Error("validation context SDS config missing sds_config")
			} else if sdsValidation.SdsConfig.GetAds() == nil {
				t.Error("validation context SDS config should use ADS")
			}
		}
	}

	if !foundSDSCluster {
		t.Error("no cluster found with SDS validation context config")
	}
}

// TestCertificateRotationTriggersSnapshot verifies that cert generation changes trigger snapshot updates.
func TestCertificateRotationTriggersSnapshot(t *testing.T) {
	// This test requires a real etcd instance - skip if not available
	if os.Getenv("ETCD_ENDPOINTS") == "" {
		t.Skip("Skipping: ETCD_ENDPOINTS not set (integration test)")
	}

	ctx := context.Background()

	// Create etcd client
	etcdClient, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{os.Getenv("ETCD_ENDPOINTS")},
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		t.Fatalf("etcd connection failed: %v", err)
	}
	defer etcdClient.Close()

	// Create test server
	xdsServer := server.New(nil, ctx)

	// Create watcher
	watcher := New(nil, xdsServer, "", "test-node", 1*time.Second, DownstreamTLSDisabled, "")
	watcher.SetEtcdClient(etcdClient)

	// Set initial certificate generation in etcd
	domain := "test.globular.internal"
	key := fmt.Sprintf("/globular/pki/bundles/%s", domain)

	bundle1 := map[string]interface{}{
		"generation": 1,
		"updated_ms": time.Now().UnixMilli(),
	}
	data1, _ := json.Marshal(bundle1)
	_, err = etcdClient.Put(ctx, key, string(data1))
	if err != nil {
		t.Fatalf("failed to set initial generation: %v", err)
	}

	// Check generation (should initialize)
	changed1 := watcher.checkCertificateGeneration(ctx)
	if changed1 {
		t.Error("first check should not detect change (initialization)")
	}

	if watcher.lastCertGeneration != 1 {
		t.Errorf("expected lastCertGeneration=1, got %d", watcher.lastCertGeneration)
	}

	// Update certificate generation
	bundle2 := map[string]interface{}{
		"generation": 2,
		"updated_ms": time.Now().UnixMilli(),
	}
	data2, _ := json.Marshal(bundle2)
	_, err = etcdClient.Put(ctx, key, string(data2))
	if err != nil {
		t.Fatalf("failed to update generation: %v", err)
	}

	// Check generation again (should detect change)
	changed2 := watcher.checkCertificateGeneration(ctx)
	if !changed2 {
		t.Error("second check should detect generation change")
	}

	if watcher.lastCertGeneration != 2 {
		t.Errorf("expected lastCertGeneration=2, got %d", watcher.lastCertGeneration)
	}

	t.Logf("✓ Certificate rotation detection works: 1 → 2")

	// Clean up
	_, _ = etcdClient.Delete(ctx, key)
}

// TestSecretContentHash verifies that secret hashes change when content changes.
func TestSecretContentHash(t *testing.T) {
	tmpDir := t.TempDir()

	// Create first certificate
	cert1Path := filepath.Join(tmpDir, "cert1.pem")
	key1Path := filepath.Join(tmpDir, "key1.pem")

	if err := os.WriteFile(cert1Path, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert1: %v", err)
	}
	if err := os.WriteFile(key1Path, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key1: %v", err)
	}

	// Build first secret
	secret1, err := controlplane.MakeSecret("test-cert", cert1Path, key1Path, "")
	if err != nil {
		t.Fatalf("MakeSecret failed: %v", err)
	}

	hash1, err := controlplane.HashSecret(secret1)
	if err != nil {
		t.Fatalf("HashSecret failed: %v", err)
	}

	// Create second certificate (different content)
	cert2Path := filepath.Join(tmpDir, "cert2.pem")
	key2Path := filepath.Join(tmpDir, "key2.pem")

	if err := os.WriteFile(cert2Path, []byte(testCert+" "), 0644); err != nil {
		t.Fatalf("write cert2: %v", err)
	}
	if err := os.WriteFile(key2Path, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key2: %v", err)
	}

	// Build second secret
	secret2, err := controlplane.MakeSecret("test-cert", cert2Path, key2Path, "")
	if err != nil {
		t.Fatalf("MakeSecret failed: %v", err)
	}

	hash2, err := controlplane.HashSecret(secret2)
	if err != nil {
		t.Fatalf("HashSecret failed: %v", err)
	}

	// Verify hashes are different
	if hash1 == hash2 {
		t.Error("different certificate content should produce different hashes")
	}

	t.Logf("✓ Secret hashes differ when content changes:")
	t.Logf("  hash1: %s", hash1[:16])
	t.Logf("  hash2: %s", hash2[:16])
}

// TestNoFileBasedTLSWhenSDSEnabled is a guard test that ensures file-based TLS
// is never used when SDS is enabled. This prevents regressions where secrets
// might be delivered via SDS but TLS contexts still reference file paths.
func TestNoFileBasedTLSWhenSDSEnabled(t *testing.T) {
	certFile := writeTestFile(t, "cert.pem", testCert)
	keyFile := writeTestFile(t, "key.pem", testKey)
	caFile := writeTestFile(t, "ca.pem", testCA)

	input := builder.Input{
		NodeID:    "test-node",
		EnableSDS: true, // SDS enabled
		SDSSecrets: []builder.Secret{
			{
				Name:     "internal-server-cert",
				CertPath: certFile,
				KeyPath:  keyFile,
			},
			{
				Name:   "internal-ca-bundle",
				CAPath: caFile,
			},
		},
		Listener: builder.Listener{
			Name:       "test_listener",
			RouteName:  "test_route",
			Host:       "0.0.0.0",
			Port:       8443,
			CertFile:   certFile,
			KeyFile:    keyFile,
			IssuerFile: caFile,
		},
		Routes: []builder.Route{
			{
				Prefix:  "/",
				Cluster: "test_cluster",
			},
		},
		Clusters: []builder.Cluster{
			{
				Name: "test_cluster",
				Endpoints: []builder.Endpoint{
					{Host: "127.0.0.1", Port: 8080},
				},
				CAFile: caFile,
			},
		},
	}

	snapshot, err := builder.BuildSnapshot(input, "v1")
	if err != nil {
		t.Fatalf("failed to build snapshot: %v", err)
	}

	// Check listeners for file-based TLS (DataSource_Filename)
	listeners := snapshot.GetResources(resource_v3.ListenerType)
	for _, res := range listeners {
		listener := res.(*listener_v3.Listener)
		for _, filterChain := range listener.FilterChains {
			if filterChain.TransportSocket == nil {
				continue
			}

			// Parse transport socket to check for file-based TLS
			var dtls tls_v3.DownstreamTlsContext
			if err := filterChain.TransportSocket.GetTypedConfig().UnmarshalTo(&dtls); err != nil {
				continue // Not a TLS socket, skip
			}

			// Check for file-based certificates (illegal when EnableSDS=true)
			if dtls.CommonTlsContext != nil {
				for _, tlsCert := range dtls.CommonTlsContext.TlsCertificates {
					if tlsCert.CertificateChain != nil {
						if _, ok := tlsCert.CertificateChain.Specifier.(*core_v3.DataSource_Filename); ok {
							t.Errorf("listener %s uses file-based TLS (DataSource_Filename) when EnableSDS=true - this is illegal", listener.Name)
						}
					}
					if tlsCert.PrivateKey != nil {
						if _, ok := tlsCert.PrivateKey.Specifier.(*core_v3.DataSource_Filename); ok {
							t.Errorf("listener %s uses file-based TLS key when EnableSDS=true - this is illegal", listener.Name)
						}
					}
				}

				// Check validation context for file-based CA
				if vc, ok := dtls.CommonTlsContext.ValidationContextType.(*tls_v3.CommonTlsContext_ValidationContext); ok {
					if vc.ValidationContext != nil && vc.ValidationContext.TrustedCa != nil {
						if _, ok := vc.ValidationContext.TrustedCa.Specifier.(*core_v3.DataSource_Filename); ok {
							t.Errorf("listener %s uses file-based CA when EnableSDS=true - this is illegal", listener.Name)
						}
					}
				}
			}
		}
	}

	// Check clusters for file-based TLS (DataSource_Filename)
	clusters := snapshot.GetResources(resource_v3.ClusterType)
	for _, res := range clusters {
		cluster := res.(*cluster_v3.Cluster)
		if cluster.TransportSocket == nil {
			continue
		}

		// Parse transport socket to check for file-based TLS
		var utls tls_v3.UpstreamTlsContext
		if err := cluster.TransportSocket.GetTypedConfig().UnmarshalTo(&utls); err != nil {
			continue // Not a TLS socket, skip
		}

		// Check for file-based certificates (illegal when EnableSDS=true)
		if utls.CommonTlsContext != nil {
			for _, tlsCert := range utls.CommonTlsContext.TlsCertificates {
				if tlsCert.CertificateChain != nil {
					if _, ok := tlsCert.CertificateChain.Specifier.(*core_v3.DataSource_Filename); ok {
						t.Errorf("cluster %s uses file-based TLS (DataSource_Filename) when EnableSDS=true - this is illegal", cluster.Name)
					}
				}
				if tlsCert.PrivateKey != nil {
					if _, ok := tlsCert.PrivateKey.Specifier.(*core_v3.DataSource_Filename); ok {
						t.Errorf("cluster %s uses file-based TLS key when EnableSDS=true - this is illegal", cluster.Name)
					}
				}
			}

			// Check validation context for file-based CA
			if vc, ok := utls.CommonTlsContext.ValidationContextType.(*tls_v3.CommonTlsContext_ValidationContext); ok {
				if vc.ValidationContext != nil && vc.ValidationContext.TrustedCa != nil {
					if _, ok := vc.ValidationContext.TrustedCa.Specifier.(*core_v3.DataSource_Filename); ok {
						t.Errorf("cluster %s uses file-based CA when EnableSDS=true - this is illegal", cluster.Name)
					}
				}
			}
		}
	}

	t.Log("✓ No file-based TLS (DataSource_Filename) found when EnableSDS=true")
}

// TestACMECertificateUsesPublicIngressCert verifies that ACME certificates
// (identified by fullchain.pem naming) use the public-ingress-cert secret name
// instead of internal-server-cert when SDS is enabled.
func TestACMECertificateUsesPublicIngressCert(t *testing.T) {
	tmpDir := t.TempDir()

	// Create ACME certificate files (using fullchain.pem naming convention)
	acmeCertPath := filepath.Join(tmpDir, "fullchain.pem")
	acmeKeyPath := filepath.Join(tmpDir, "privkey.pem")

	if err := os.WriteFile(acmeCertPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write ACME cert: %v", err)
	}
	if err := os.WriteFile(acmeKeyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write ACME key: %v", err)
	}

	// Build input with SDS enabled and ACME certificate
	input := builder.Input{
		NodeID:    "test-node",
		EnableSDS: true,
		SDSSecrets: []builder.Secret{
			{
				Name:     "public-ingress-cert",
				CertPath: acmeCertPath,
				KeyPath:  acmeKeyPath,
			},
		},
		Listener: builder.Listener{
			Name:      "public-ingress-listener",
			Port:      443,
			RouteName: "public-routes",
			CertFile:  acmeCertPath, // Contains "fullchain.pem"
			KeyFile:   acmeKeyPath,
		},
		Routes: []builder.Route{
			{Prefix: "/", Cluster: "test-cluster"},
		},
		Clusters: []builder.Cluster{
			{
				Name:      "test-cluster",
				Endpoints: []builder.Endpoint{{Host: "localhost", Port: 8080}},
			},
		},
	}

	// Build snapshot
	snapshot, err := builder.BuildSnapshot(input, "v1")
	if err != nil {
		t.Fatalf("BuildSnapshot failed: %v", err)
	}

	// Verify snapshot contains public-ingress-cert secret
	secrets := snapshot.GetResources(resource_v3.SecretType)
	if _, exists := secrets["public-ingress-cert"]; !exists {
		t.Error("snapshot should contain public-ingress-cert for ACME certificate")
	}

	// Get listener from snapshot
	listeners := snapshot.GetResources(resource_v3.ListenerType)
	if len(listeners) == 0 {
		t.Fatal("snapshot should contain listeners")
	}

	// Verify listener uses public-ingress-cert secret (not internal-server-cert)
	foundPublicIngressSecret := false
	for _, resource := range listeners {
		listener, ok := resource.(*listener_v3.Listener)
		if !ok {
			continue
		}

		// Check filter chains for TLS config
		for _, chain := range listener.FilterChains {
			if chain.TransportSocket == nil {
				continue
			}

			// Unmarshal the transport socket config
			var tlsContext tls_v3.DownstreamTlsContext
			if err := chain.TransportSocket.GetTypedConfig().UnmarshalTo(&tlsContext); err != nil {
				t.Logf("failed to unmarshal TLS context: %v", err)
				continue
			}

			if tlsContext.CommonTlsContext == nil {
				continue
			}

			// Verify SDS secret configs reference public-ingress-cert
			sdsConfigs := tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs
			if len(sdsConfigs) == 0 {
				t.Error("ACME listener should have tls_certificate_sds_secret_configs")
				continue
			}

			secretName := sdsConfigs[0].Name
			if secretName == "public-ingress-cert" {
				foundPublicIngressSecret = true
				t.Logf("✓ ACME listener uses public-ingress-cert secret")
			} else if secretName == "internal-server-cert" {
				t.Error("ACME listener (fullchain.pem) should use public-ingress-cert, not internal-server-cert")
			} else {
				t.Errorf("ACME listener has unexpected secret name: %s", secretName)
			}

			// Verify ADS config
			if sdsConfigs[0].SdsConfig == nil {
				t.Error("SDS config missing sds_config")
			} else if sdsConfigs[0].SdsConfig.GetAds() == nil {
				t.Error("SDS config should use ADS")
			}

			// Verify no file-based TLS
			for _, tlsCert := range tlsContext.CommonTlsContext.TlsCertificates {
				if tlsCert.CertificateChain != nil {
					if _, ok := tlsCert.CertificateChain.Specifier.(*core_v3.DataSource_Filename); ok {
						t.Error("ACME listener uses file-based TLS when SDS is enabled - this is illegal")
					}
				}
				if tlsCert.PrivateKey != nil {
					if _, ok := tlsCert.PrivateKey.Specifier.(*core_v3.DataSource_Filename); ok {
						t.Error("ACME listener uses file-based TLS key when SDS is enabled - this is illegal")
					}
				}
			}
		}
	}

	if !foundPublicIngressSecret {
		t.Error("no listener found using public-ingress-cert secret for ACME certificate")
	}
}

// TestACMERotationDetectsFileChanges verifies that ACME certificate file changes
// are detected via hash comparison.
func TestACMERotationDetectsFileChanges(t *testing.T) {
	tmpDir := t.TempDir()

	// Create initial ACME certificate files
	acmeCertPath := filepath.Join(tmpDir, "fullchain.pem")
	acmeKeyPath := filepath.Join(tmpDir, "privkey.pem")

	if err := os.WriteFile(acmeCertPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(acmeKeyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key: %v", err)
	}

	// Create watcher
	watcher := &Watcher{
		logger: nil,
	}

	// First check - should initialize hashes, not trigger rotation
	changed1 := watcher.checkACMECertificateRotation(acmeCertPath, acmeKeyPath)
	if changed1 {
		t.Error("first check should not detect change (initialization)")
	}

	if watcher.lastACMECertHash == "" || watcher.lastACMEKeyHash == "" {
		t.Error("hashes should be initialized after first check")
	}

	// Second check with same files - should not trigger rotation
	changed2 := watcher.checkACMECertificateRotation(acmeCertPath, acmeKeyPath)
	if changed2 {
		t.Error("second check with unchanged files should not trigger rotation")
	}

	// Modify certificate file (simulate ACME renewal)
	if err := os.WriteFile(acmeCertPath, []byte(testCert+" renewed"), 0644); err != nil {
		t.Fatalf("write renewed cert: %v", err)
	}

	// Third check - should detect certificate change
	changed3 := watcher.checkACMECertificateRotation(acmeCertPath, acmeKeyPath)
	if !changed3 {
		t.Error("third check should detect certificate renewal")
	}

	t.Log("✓ ACME certificate rotation detection works via file hash changes")

	// Modify key file
	if err := os.WriteFile(acmeKeyPath, []byte(testKey+" renewed"), 0600); err != nil {
		t.Fatalf("write renewed key: %v", err)
	}

	// Fourth check - should detect key change
	changed4 := watcher.checkACMECertificateRotation(acmeCertPath, acmeKeyPath)
	if !changed4 {
		t.Error("fourth check should detect key file change")
	}

	t.Log("✓ ACME key rotation detection works via file hash changes")
}

// TestACMERotationHandlesInvalidFiles verifies that invalid/missing ACME files
// don't trigger rotation and don't break the watcher.
func TestACMERotationHandlesInvalidFiles(t *testing.T) {
	tmpDir := t.TempDir()

	certPath := filepath.Join(tmpDir, "fullchain.pem")
	keyPath := filepath.Join(tmpDir, "privkey.pem")

	// Create watcher
	watcher := &Watcher{
		logger: nil,
	}

	// Check with missing files - should not panic or trigger rotation
	changed := watcher.checkACMECertificateRotation(certPath, keyPath)
	if changed {
		t.Error("check with missing files should not trigger rotation")
	}

	// Create initial valid files
	if err := os.WriteFile(certPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(keyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key: %v", err)
	}

	// Initialize hashes
	changed = watcher.checkACMECertificateRotation(certPath, keyPath)
	if changed {
		t.Error("initialization should not trigger rotation")
	}

	oldCertHash := watcher.lastACMECertHash
	oldKeyHash := watcher.lastACMEKeyHash

	// Simulate partial ACME renewal (cert file becomes unreadable)
	if err := os.Remove(certPath); err != nil {
		t.Fatalf("remove cert: %v", err)
	}

	// Check with missing cert - should not trigger rotation, should keep old hashes
	changed = watcher.checkACMECertificateRotation(certPath, keyPath)
	if changed {
		t.Error("check with missing cert should not trigger rotation (keep old secret)")
	}

	// Verify old hashes are preserved (we don't lose the last known good state)
	if watcher.lastACMECertHash != oldCertHash || watcher.lastACMEKeyHash != oldKeyHash {
		t.Error("hashes should not change when files become unreadable")
	}

	t.Log("✓ ACME rotation safely handles invalid/missing files")
}

// TestACMERotationHandlesCorruptPEM ensures readable-but-invalid PEM does not advance hashes
// and the last good certificate remains in use.
func TestACMERotationHandlesCorruptPEM(t *testing.T) {
	tmpDir := t.TempDir()

	certPath := filepath.Join(tmpDir, "fullchain.pem")
	keyPath := filepath.Join(tmpDir, "privkey.pem")

	// Write valid initial cert/key
	if err := os.WriteFile(certPath, []byte(testCert), 0644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(keyPath, []byte(testKey), 0600); err != nil {
		t.Fatalf("write key: %v", err)
	}

	watcher := &Watcher{}

	// Initialize hashes with valid files
	if watcher.checkACMECertificateRotation(certPath, keyPath) {
		t.Fatal("initialization should not trigger rotation")
	}
	initialCertHash := watcher.lastACMECertHash
	initialKeyHash := watcher.lastACMEKeyHash

	// Corrupt the cert (readable but invalid PEM)
	if err := os.WriteFile(certPath, []byte("NOT A CERT"), 0644); err != nil {
		t.Fatalf("write corrupt cert: %v", err)
	}

	changed := watcher.checkACMECertificateRotation(certPath, keyPath)
	if changed {
		t.Error("corrupt cert should not trigger rotation")
	}

	// Hashes must remain unchanged
	if watcher.lastACMECertHash != initialCertHash || watcher.lastACMEKeyHash != initialKeyHash {
		t.Error("hashes should remain at last good values after corrupt renewal")
	}

	// Secret build should fail for corrupt PEM
	if _, err := controlplane.MakeSecret(secrets.PublicIngressCert, certPath, keyPath, ""); err == nil {
		t.Error("expected MakeSecret to fail for corrupt ACME cert PEM")
	}
}

// Helper functions

func writeTestFile(t *testing.T, filename, content string) string {
	t.Helper()
	tmpDir := t.TempDir()
	path := filepath.Join(tmpDir, filename)
	if err := os.WriteFile(path, []byte(content), 0600); err != nil {
		t.Fatalf("failed to write test file %s: %v", filename, err)
	}
	return path
}

func getSecretNames(secrets map[string]types.Resource) []string {
	names := make([]string, 0, len(secrets))
	for name := range secrets {
		names = append(names, name)
	}
	return names
}

// generateTestCertKey creates a self-signed RSA certificate and returns cert, key, and CA PEM.
// For test purposes the CA is the same self-signed cert.
func generateTestCertKey() (certPEM, keyPEM, caPEM []byte) {
	priv, _ := rsa.GenerateKey(rand.Reader, 2048)
	serial, _ := rand.Int(rand.Reader, big.NewInt(1<<62))
	tpl := &x509.Certificate{
		SerialNumber:          serial,
		Subject:               pkix.Name{CommonName: "test.local"},
		NotBefore:             time.Now().Add(-time.Hour),
		NotAfter:              time.Now().Add(24 * time.Hour),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		IsCA:                  true,
		BasicConstraintsValid: true,
	}
	der, _ := x509.CreateCertificate(rand.Reader, tpl, tpl, &priv.PublicKey, priv)

	certBuf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
	keyBuf := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})

	return certBuf, keyBuf, certBuf
}
