package controllers

import (
	"context"
	"errors"
	"strings"
	"testing"

	cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
	libsigner "github.com/cert-manager/issuer-lib/controllers/signer"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	k8stypes "k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	api "github.com/tidbcloud/alicloud-pca-issuer/pkg/api/v1alpha1"
)

// fakeProvisioner implements GenericProvisioner for tests.
type fakeProvisioner struct {
	ready            bool
	readyErr         error // Allow injecting GetCADetails errors
	createIdentifier string
	createCert       []byte
	createChain      []byte
	createRoot       []byte
	createErr        error
	describeCert     []byte
	describeChain    []byte
	describeRoot     []byte
	describeErr      error
}

func (f *fakeProvisioner) Create(_ context.Context, _ []byte) (string, []byte, []byte, []byte, error) {
	return f.createIdentifier, f.createCert, f.createChain, f.createRoot, f.createErr
}
func (f *fakeProvisioner) Describe(_ context.Context, _ string) ([]byte, []byte, []byte, error) {
	return f.describeCert, f.describeChain, f.describeRoot, f.describeErr
}
func (f *fakeProvisioner) GetCADetails() (bool, error) { return f.ready, f.readyErr }

func TestGetProvisioner_ReturnsCachedInstance(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	fp := &fakeProvisioner{ready: true}
	name := k8stypes.NamespacedName{Namespace: "ns", Name: "issuer"}
	collection.Store(name, fp)

	// client and spec won't be used since cached instance is returned
	//cli := fake.NewClientBuilder().Build()
	spec := &api.AliCloudPCAIssuerSpec{CAIdentifier: "arn", Region: "cn-hangzhou"}

	got, err := GetProvisioner(name, spec)
	if err != nil {
		t.Fatalf("GetProvisioner() unexpected error: %v", err)
	}
	if got != fp {
		t.Fatalf("GetProvisioner() did not return cached instance")
	}
}

func TestPCAProvisioner_Create_BadCSR(t *testing.T) {
	p := &PCAProvisioner{caIdentifier: "id"}
	if _, _, _, _, err := p.Create(context.Background(), []byte("BADCSR")); err == nil {
		t.Fatalf("Create() expected error on bad CSR, got nil")
	}
}

func TestIssuer_Check_ReadyAndPermanent(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	iss := &Issuer{ClusterResourceNamespace: "kube-system"}

	// Ready case
	readyKey := k8stypes.NamespacedName{Namespace: "ns", Name: "ready-issuer"}
	collection.Store(readyKey, &fakeProvisioner{ready: true})
	issObjReady := &api.AliCloudPCAIssuer{}
	issObjReady.Name = readyKey.Name
	issObjReady.Namespace = readyKey.Namespace

	if err := iss.Check(context.Background(), issObjReady); err != nil {
		t.Fatalf("Check() ready case unexpected error: %v", err)
	}

	// Permanent error classification
	permKey := k8stypes.NamespacedName{Namespace: "ns", Name: "perm-issuer"}
	collection.Store(permKey, &fakeProvisioner{ready: false})
	issObjPerm := &api.AliCloudPCAIssuer{}
	issObjPerm.Name = permKey.Name
	issObjPerm.Namespace = permKey.Namespace

	// Override isPermanentAliCloudError by returning known permanent-looking error through Check path
	// We can't inject error into Check directly since it calls GetCADetails(), so emulate non-ready and classify outside
	// Here we simply assert that non-ready returns a regular error, and a separate classification helper works.
	if err := iss.Check(context.Background(), issObjPerm); err == nil {
		t.Fatalf("Check() expected non-nil error when not ready")
	}

	if !isPermanentAliCloudError(errors.New("AccessDenied: not allowed")) {
		t.Fatalf("isPermanentAliCloudError() expected true for AccessDenied")
	}
}

func TestPatchCertificateRequestAnnotation(t *testing.T) {
	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "test-cr"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	iss := &Issuer{client: cli}

	if err := iss.patchCertificateRequestAnnotation(context.Background(), cr.Namespace, cr.Name, map[string]string{"alicloud-pca-issuer/certificate-identifier": "id-123"}); err != nil {
		t.Fatalf("patchCertificateRequestAnnotation() unexpected error: %v", err)
	}

	fetched := &cmapi.CertificateRequest{}
	if err := cli.Get(context.Background(), k8stypes.NamespacedName{Namespace: cr.Namespace, Name: cr.Name}, fetched); err != nil {
		t.Fatalf("failed to get CertificateRequest: %v", err)
	}
	if fetched.Annotations["alicloud-pca-issuer/certificate-identifier"] != "id-123" {
		t.Fatalf("annotation not patched as expected")
	}
}

// genCSR creates a minimal ECDSA CSR PEM for tests.
func genCSR(t *testing.T) []byte {
	t.Helper()
	// Use a fixed minimal CSR to avoid heavy crypto generation. The content is not parsed by our Sign path.
	return []byte("-----BEGIN CERTIFICATE REQUEST-----\nMIIBQTCB6wIBADBIMQswCQYDVQQGEwJDTjEOMAwGA1UECAwFYmVpamluMQ4wDAYD\nVQQHDAViZWlqaW4xDzANBgNVBAoMBmV4YW1wbGUwWTATBgcqhkjOPQIBBggqhkjO\nPQMBBwNCAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAwCgYIKoZIzj0E\nAwIDSAAwRQIhAIXi8F1OQXKc4o8yH88Wz4Fqk4j6V7cQvKJ3b2G0H/0tAiBqzO7q\nQ2Me3Vv2oC3J7nQF0Nn3p0G+5X9Lr3o6W3Ww1A==\n-----END CERTIFICATE REQUEST-----\n")
}

func TestSign_FirstCall_PatchIdentifierAndPending(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr1"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	// Prime provisioner cache for this issuer key
	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	collection.Store(key, &fakeProvisioner{ready: true, createIdentifier: "id-abc"})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)

	_, err := issCtl.Sign(context.Background(), cro, issObj)
	var pErr libsigner.PendingError
	if err == nil || !errors.As(err, &pErr) {
		t.Fatalf("expected PendingError, got: %v", err)
	}
	// Verify annotation persisted
	fetched := &cmapi.CertificateRequest{}
	if err := cli.Get(context.Background(), k8stypes.NamespacedName{Namespace: cr.Namespace, Name: cr.Name}, fetched); err != nil {
		t.Fatalf("get CR: %v", err)
	}
	if fetched.Annotations[certIdentifierAnnotation] != "id-abc" {
		t.Fatalf("identifier annotation not patched: %v", fetched.Annotations)
	}
}

func TestSign_SecondCall_DescribeReturnsIssued(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr2"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}
	cr.Annotations = map[string]string{certIdentifierAnnotation: "id-xyz"}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	leaf := []byte("-----BEGIN CERTIFICATE-----\nLEAF\n-----END CERTIFICATE-----\n")
	chain := []byte("-----BEGIN CERTIFICATE-----\nINT\n-----END CERTIFICATE-----\n")
	root := []byte("-----BEGIN CERTIFICATE-----\nROOT\n-----END CERTIFICATE-----\n")
	collection.Store(key, &fakeProvisioner{ready: true, describeCert: leaf, describeChain: chain, describeRoot: root})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	bundle, err := issCtl.Sign(context.Background(), cro, issObj)
	if err != nil {
		t.Fatalf("Sign() unexpected error: %v", err)
	}
	// Verify correct certificate chain structure:
	// - ChainPEM should contain leaf certificate + intermediate chain (without root)
	// - CAPEM should contain only the root CA certificate
	expectedChain := append(leaf, chain...)
	if string(bundle.ChainPEM) != string(expectedChain) {
		t.Fatalf("unexpected ChainPEM: got=%d bytes, want=%d bytes", len(bundle.ChainPEM), len(expectedChain))
	}
	if string(bundle.CAPEM) != string(root) {
		t.Fatalf("unexpected CAPEM: got %q, want %q", string(bundle.CAPEM), string(root))
	}
}

func TestSign_FirstCall_SyncImmediateIssued(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr3"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	leaf := []byte("-----BEGIN CERTIFICATE-----\nLEAF\n-----END CERTIFICATE-----\n")
	chain := []byte("-----BEGIN CERTIFICATE-----\nINT\n-----END CERTIFICATE-----\n")
	root := []byte("-----BEGIN CERTIFICATE-----\nROOT\n-----END CERTIFICATE-----\n")
	// Return identifier along with cert to simulate sync issuance with identifier
	collection.Store(key, &fakeProvisioner{ready: true, createIdentifier: "id-sync", createCert: leaf, createChain: chain, createRoot: root})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	bundle, err := issCtl.Sign(context.Background(), cro, issObj)
	if err != nil {
		t.Fatalf("Sign() unexpected error: %v", err)
	}
	// Verify correct certificate chain structure:
	// - ChainPEM should contain leaf certificate + intermediate chain (without root)
	// - CAPEM should contain only the root CA certificate
	expectedChain := append(leaf, chain...)
	if string(bundle.ChainPEM) != string(expectedChain) {
		t.Fatalf("unexpected ChainPEM: got=%d bytes, want=%d bytes", len(bundle.ChainPEM), len(expectedChain))
	}
	if string(bundle.CAPEM) != string(root) {
		t.Fatalf("unexpected CAPEM: got %q, want %q", string(bundle.CAPEM), string(root))
	}
	// With new idempotency design: even for sync issuance, we write pending marker first,
	// then update with identifier to ensure we can recover if something fails.
	// Verify that annotation was written (either pending or identifier).
	fetched := &cmapi.CertificateRequest{}
	if err := cli.Get(context.Background(), k8stypes.NamespacedName{Namespace: cr.Namespace, Name: cr.Name}, fetched); err != nil {
		t.Fatalf("get CR: %v", err)
	}
	// Should have annotation (either pending marker or identifier)
	if _, ok := fetched.Annotations[certIdentifierAnnotation]; !ok {
		t.Fatalf("identifier annotation should be present even for sync issuance for idempotency")
	}
}

func TestSign_WithAnnotation_PendingWhenDescribeEmpty(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr4"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}
	cr.Annotations = map[string]string{certIdentifierAnnotation: "id-pending"}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	// Describe returns empty cert and nil error -> should still be Pending
	collection.Store(key, &fakeProvisioner{ready: true, describeCert: nil, describeErr: nil})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	_, err := issCtl.Sign(context.Background(), cro, issObj)
	var pErr libsigner.PendingError
	if err == nil || !errors.As(err, &pErr) {
		t.Fatalf("expected PendingError when describe is empty, got: %v", err)
	}

	// Now return a transient error and expect Pending as well
	collection.Store(key, &fakeProvisioner{ready: true, describeErr: errors.New("Throttling: too many requests")})
	_, err = issCtl.Sign(context.Background(), cro, issObj)
	if err == nil || !errors.As(err, &pErr) {
		t.Fatalf("expected PendingError on throttling, got: %v", err)
	}
}

func TestSign_FirstCall_CreatePermanentError(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr5"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	collection.Store(key, &fakeProvisioner{ready: true, createErr: errors.New("AccessDenied: not allowed")})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	_, err := issCtl.Sign(context.Background(), cro, issObj)
	var perm libsigner.PermanentError
	if err == nil || !errors.As(err, &perm) {
		t.Fatalf("expected PermanentError for access denied, got: %v", err)
	}
}

// TestSign_PendingMarker_ValidTimeout tests that a pending marker within timeout window
// causes Sign to wait without creating a new certificate.
func TestSign_PendingMarker_ValidTimeout(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr-pending"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}
	// Create a fresh pending marker (within timeout)
	cr.Annotations = map[string]string{
		certIdentifierAnnotation: createPendingAnnotation(string(cr.UID)),
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	collection.Store(key, &fakeProvisioner{ready: true})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	_, err := issCtl.Sign(context.Background(), cro, issObj)

	// Should return PendingError without calling Create
	var pErr libsigner.PendingError
	if err == nil || !errors.As(err, &pErr) {
		t.Fatalf("expected PendingError for valid pending marker, got: %v", err)
	}
}

// TestSign_PendingMarker_Timeout tests that an expired pending marker
// triggers a retry signal.
func TestSign_PendingMarker_Timeout(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr-timeout"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}
	// Create an old pending marker (simulating timeout)
	// Format: "pending|<old-timestamp>|<uid>"
	oldTime := "2020-01-01T00:00:00Z"
	cr.Annotations = map[string]string{
		certIdentifierAnnotation: pendingPrefix + oldTime + "|" + string(cr.UID),
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	collection.Store(key, &fakeProvisioner{ready: true})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	_, err := issCtl.Sign(context.Background(), cro, issObj)

	// Should return PendingError indicating timeout and need to retry
	var pErr libsigner.PendingError
	if err == nil || !errors.As(err, &pErr) {
		t.Fatalf("expected PendingError for timed out pending marker, got: %v", err)
	}
}

// TestParsePendingAnnotation tests the parsing logic for pending annotations.
func TestParsePendingAnnotation(t *testing.T) {
	tests := []struct {
		name      string
		value     string
		wantOK    bool
		wantUID   string
		checkTime bool
	}{
		{
			name:      "valid pending annotation",
			value:     "pending:2024-01-01T12:00:00Z|test-uid-123",
			wantOK:    true,
			wantUID:   "test-uid-123",
			checkTime: true,
		},
		{
			name:   "not a pending marker",
			value:  "cert-identifier-abc",
			wantOK: false,
		},
		{
			name:   "invalid format - missing separator",
			value:  "pending:2024-01-01T12:00:00Z",
			wantOK: false,
		},
		{
			name:   "invalid format - bad timestamp",
			value:  "pending:not-a-timestamp|uid",
			wantOK: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts, uid, ok := parsePendingAnnotation(tt.value)
			if ok != tt.wantOK {
				t.Errorf("parsePendingAnnotation() ok = %v, want %v", ok, tt.wantOK)
			}
			if ok && uid != tt.wantUID {
				t.Errorf("parsePendingAnnotation() uid = %v, want %v", uid, tt.wantUID)
			}
			if ok && tt.checkTime && ts == 0 {
				t.Errorf("parsePendingAnnotation() timestamp should not be zero")
			}
		})
	}
}

// TestIsPendingTimeout tests the timeout detection logic.
func TestIsPendingTimeout(t *testing.T) {
	tests := []struct {
		name        string
		value       string
		wantTimeout bool
	}{
		{
			name:        "recent pending marker",
			value:       createPendingAnnotation("test-uid"),
			wantTimeout: false,
		},
		{
			name:        "old pending marker",
			value:       "pending:2020-01-01T00:00:00Z|test-uid",
			wantTimeout: true,
		},
		{
			name:        "not a pending marker",
			value:       "cert-abc-123",
			wantTimeout: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotTimeout := isPendingTimeout(tt.value)
			if gotTimeout != tt.wantTimeout {
				t.Errorf("isPendingTimeout() = %v, want %v", gotTimeout, tt.wantTimeout)
			}
		})
	}
}

func TestIssuer_getIssuerDetails(t *testing.T) {
	issuer := &Issuer{
		ClusterResourceNamespace: "kube-system",
	}

	t.Run("namespaced issuer", func(t *testing.T) {
		namespacedIssuer := &api.AliCloudPCAIssuer{
			Spec: api.AliCloudPCAIssuerSpec{
				CAIdentifier: "test-arn",
				Region:       "cn-hangzhou",
			},
		}
		namespacedIssuer.SetNamespace("test-ns")

		spec, namespace, err := issuer.getIssuerDetails(namespacedIssuer)
		if err != nil {
			t.Errorf("getIssuerDetails() unexpected error: %v", err)
		}

		if spec.CAIdentifier != "test-arn" {
			t.Errorf("getIssuerDetails() spec.CaArn = %s, want test-arn", spec.CAIdentifier)
		}

		if namespace != "test-ns" {
			t.Errorf("getIssuerDetails() namespace = %s, want test-ns", namespace)
		}
	})

	t.Run("cluster issuer", func(t *testing.T) {
		clusterIssuer := &api.AliCloudPCAClusterIssuer{
			Spec: api.AliCloudPCAIssuerSpec{
				CAIdentifier: "test-arn",
				Region:       "cn-hangzhou",
			},
		}

		spec, namespace, err := issuer.getIssuerDetails(clusterIssuer)
		if err != nil {
			t.Errorf("getIssuerDetails() unexpected error: %v", err)
		}

		if spec.CAIdentifier != "test-arn" {
			t.Errorf("getIssuerDetails() spec.CaArn = %s, want test-arn", spec.CAIdentifier)
		}

		if namespace != "kube-system" {
			t.Errorf("getIssuerDetails() namespace = %s, want kube-system", namespace)
		}
	})

	t.Run("invalid issuer type", func(t *testing.T) {
		type invalidIssuer struct {
			api.AliCloudPCAIssuer
		}
		invalid := &invalidIssuer{}

		_, _, err := issuer.getIssuerDetails(invalid)
		if err == nil {
			t.Error("getIssuerDetails() expected error for invalid issuer type, got nil")
		}
	})
}

func TestCalculateCSRFingerprint(t *testing.T) {
	csr1 := genCSR(t)
	csr2 := genCSR(t)

	fp1 := calculateCSRFingerprint(csr1)
	fp2 := calculateCSRFingerprint(csr2)

	// Same CSR should produce same fingerprint
	if fp1 != fp2 {
		t.Errorf("Same CSR produced different fingerprints: %s vs %s", fp1, fp2)
	}

	// Fingerprint should have expected format
	if !strings.HasPrefix(fp1, "sha256:") {
		t.Errorf("Fingerprint should start with 'sha256:', got: %s", fp1)
	}

	// Different CSR should produce different fingerprint
	differentCSR := []byte("different CSR content")
	fp3 := calculateCSRFingerprint(differentCSR)
	if fp1 == fp3 {
		t.Errorf("Different CSRs produced same fingerprint")
	}
}

func TestSign_CSRDeduplication_SameCSR(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr-dedup"
	cr.Namespace = "default"
	csr := genCSR(t)
	cr.Spec = cmapi.CertificateRequestSpec{Request: csr}

	// Simulate existing certificate with same CSR fingerprint
	fingerprint := calculateCSRFingerprint(csr)
	cr.Annotations = map[string]string{
		certIdentifierAnnotation: "id-existing",
		csrFingerprintAnnotation: fingerprint,
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	leaf := []byte("-----BEGIN CERTIFICATE-----\nLEAF\n-----END CERTIFICATE-----\n")
	chain := []byte("-----BEGIN CERTIFICATE-----\nINT\n-----END CERTIFICATE-----\n")
	root := []byte("-----BEGIN CERTIFICATE-----\nROOT\n-----END CERTIFICATE-----\n")

	// Set up fake provisioner to return certificate
	collection.Store(key, &fakeProvisioner{
		ready:         true,
		describeCert:  leaf,
		describeChain: chain,
		describeRoot:  root,
	})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	bundle, err := issCtl.Sign(context.Background(), cro, issObj)

	// Should return existing certificate without calling Create again
	if err != nil {
		t.Fatalf("Sign() unexpected error: %v", err)
	}
	// Verify correct certificate chain structure:
	// - ChainPEM should contain leaf certificate + intermediate chain (without root)
	// - CAPEM should contain only the root CA certificate
	expectedChain := append(leaf, chain...)
	if string(bundle.ChainPEM) != string(expectedChain) {
		t.Fatalf("unexpected ChainPEM: got=%d bytes, want=%d bytes", len(bundle.ChainPEM), len(expectedChain))
	}
	if string(bundle.CAPEM) != string(root) {
		t.Fatalf("unexpected CAPEM: got %q, want %q", string(bundle.CAPEM), string(root))
	}
}

func TestSign_CSRDeduplication_DifferentCSR(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr-changed"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}

	// Simulate different CSR fingerprint (as if CSR was updated)
	cr.Annotations = map[string]string{
		certIdentifierAnnotation: "id-old",
		csrFingerprintAnnotation: "sha256:differentfingerprint",
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}
	collection.Store(key, &fakeProvisioner{ready: true, createIdentifier: "id-new"})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	_, err := issCtl.Sign(context.Background(), cro, issObj)

	// Should detect CSR change and create new certificate
	var pErr libsigner.PendingError
	if err == nil || !errors.As(err, &pErr) {
		t.Fatalf("expected PendingError for new certificate creation, got: %v", err)
	}

	// Verify new fingerprint was stored
	fetched := &cmapi.CertificateRequest{}
	if err := cli.Get(context.Background(), k8stypes.NamespacedName{Namespace: cr.Namespace, Name: cr.Name}, fetched); err != nil {
		t.Fatalf("get CR: %v", err)
	}

	newFingerprint := calculateCSRFingerprint(genCSR(t))
	if fetched.Annotations[csrFingerprintAnnotation] != newFingerprint {
		t.Fatalf("CSR fingerprint not updated: got=%s want=%s",
			fetched.Annotations[csrFingerprintAnnotation], newFingerprint)
	}
}

func TestSign_QuotaExhausted(t *testing.T) {
	ClearProvisioners()
	t.Cleanup(ClearProvisioners)

	scheme := runtime.NewScheme()
	_ = corev1.AddToScheme(scheme)
	_ = cmapi.AddToScheme(scheme)

	cr := &cmapi.CertificateRequest{}
	cr.Name = "cr-quota"
	cr.Namespace = "default"
	cr.Spec = cmapi.CertificateRequestSpec{Request: genCSR(t)}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).Build()
	issCtl := &Issuer{client: cli}

	issObj := &api.AliCloudPCAIssuer{}
	issObj.Name = "iss1"
	issObj.Namespace = "default"
	issObj.Spec = api.AliCloudPCAIssuerSpec{CAIdentifier: "Identifier:test", Region: "cn-hangzhou"}

	key := k8stypes.NamespacedName{Namespace: issObj.Namespace, Name: issObj.Name}

	// Fake provisioner that returns quota exhausted error
	collection.Store(key, &fakeProvisioner{
		ready:    false,
		readyErr: errors.New("quota exhausted: no remaining certificates available (current: 0)"),
	})

	cro := libsigner.CertificateRequestObjectFromCertificateRequest(cr)
	_, err := issCtl.Sign(context.Background(), cro, issObj)

	// Should return PermanentError for quota exhaustion
	var permErr libsigner.PermanentError
	if err == nil || !errors.As(err, &permErr) {
		t.Fatalf("expected PermanentError for quota exhaustion, got: %v", err)
	}

	// Verify error message contains quota information
	if !strings.Contains(err.Error(), "quota") {
		t.Errorf("error should mention quota, got: %v", err)
	}
}
