package controllers

import (
	"context"
	"testing"

	cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
	"github.com/go-logr/logr"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

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

	tests := []struct {
		name           string
		cr             *cmapi.CertificateRequest
		expectApproved bool
		expectRequeue  bool
	}{
		{
			name: "approve certificate request for alicloud pca issuer",
			cr: &cmapi.CertificateRequest{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cr",
					Namespace: "default",
				},
				Spec: cmapi.CertificateRequestSpec{
					IssuerRef: cmmeta.ObjectReference{
						Group: "alicloudpca.cert-manager.io",
						Kind:  "AliCloudPCAIssuer",
						Name:  "test-issuer",
					},
					Request: []byte("test-csr"),
				},
			},
			expectApproved: true,
			expectRequeue:  false,
		},
		{
			name: "skip certificate request for different issuer",
			cr: &cmapi.CertificateRequest{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cr",
					Namespace: "default",
				},
				Spec: cmapi.CertificateRequestSpec{
					IssuerRef: cmmeta.ObjectReference{
						Group: "cert-manager.io",
						Kind:  "Issuer",
						Name:  "test-issuer",
					},
					Request: []byte("test-csr"),
				},
			},
			expectApproved: false,
			expectRequeue:  false,
		},
		{
			name: "skip already approved certificate request",
			cr: &cmapi.CertificateRequest{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cr",
					Namespace: "default",
				},
				Spec: cmapi.CertificateRequestSpec{
					IssuerRef: cmmeta.ObjectReference{
						Group: "alicloudpca.cert-manager.io",
						Kind:  "AliCloudPCAIssuer",
						Name:  "test-issuer",
					},
					Request: []byte("test-csr"),
				},
				Status: cmapi.CertificateRequestStatus{
					Conditions: []cmapi.CertificateRequestCondition{
						{
							Type:   cmapi.CertificateRequestConditionApproved,
							Status: cmmeta.ConditionTrue,
						},
					},
				},
			},
			expectApproved: true,
			expectRequeue:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeClient := fake.NewClientBuilder().
				WithScheme(scheme).
				WithObjects(tt.cr).
				WithStatusSubresource(tt.cr).
				Build()

			r := &CertificateRequestApprover{
				Client: fakeClient,
				Log:    logr.Discard(),
				Scheme: scheme,
			}

			req := reconcile.Request{
				NamespacedName: types.NamespacedName{
					Name:      tt.cr.Name,
					Namespace: tt.cr.Namespace,
				},
			}

			result, err := r.Reconcile(context.TODO(), req)
			if err != nil {
				t.Errorf("Reconcile() error = %v", err)
				return
			}

			if result.Requeue != tt.expectRequeue {
				t.Errorf("Reconcile() requeue = %v, want %v", result.Requeue, tt.expectRequeue)
			}

			// Fetch the CertificateRequest to check if it was approved
			var updatedCR cmapi.CertificateRequest
			if err := fakeClient.Get(context.TODO(), req.NamespacedName, &updatedCR); err != nil {
				t.Errorf("failed to get updated CertificateRequest: %v", err)
				return
			}

			approved := isApproved(&updatedCR)
			if approved != tt.expectApproved {
				t.Errorf("CertificateRequest approved = %v, want %v", approved, tt.expectApproved)
			}
		})
	}
}

func TestIsApproved(t *testing.T) {
	tests := []struct {
		name string
		cr   *cmapi.CertificateRequest
		want bool
	}{
		{
			name: "approved certificate request",
			cr: &cmapi.CertificateRequest{
				Status: cmapi.CertificateRequestStatus{
					Conditions: []cmapi.CertificateRequestCondition{
						{
							Type:   cmapi.CertificateRequestConditionApproved,
							Status: cmmeta.ConditionTrue,
						},
					},
				},
			},
			want: true,
		},
		{
			name: "not approved certificate request",
			cr: &cmapi.CertificateRequest{
				Status: cmapi.CertificateRequestStatus{
					Conditions: []cmapi.CertificateRequestCondition{},
				},
			},
			want: false,
		},
		{
			name: "denied certificate request",
			cr: &cmapi.CertificateRequest{
				Status: cmapi.CertificateRequestStatus{
					Conditions: []cmapi.CertificateRequestCondition{
						{
							Type:   cmapi.CertificateRequestConditionDenied,
							Status: cmmeta.ConditionTrue,
						},
					},
				},
			},
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isApproved(tt.cr); got != tt.want {
				t.Errorf("isApproved() = %v, want %v", got, tt.want)
			}
		})
	}
}
