package analyzer

import (
	"context"
	"testing"

	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
	"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
	"github.com/magiconair/properties/assert"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/scheme"
	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
	gtwapi "sigs.k8s.io/gateway-api/apis/v1"
)

func BuildGatewayClass(name string) gtwapi.GatewayClass {
	GatewayClass := gtwapi.GatewayClass{}
	GatewayClass.Name = name
	// Namespace is not needed outside of this test, GatewayClass is cluster-scoped
	GatewayClass.Namespace = "default"
	GatewayClass.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"

	return GatewayClass
}

func BuildGateway(className gtwapi.ObjectName, status metav1.ConditionStatus, labels map[string]string) gtwapi.Gateway {
	Gateway := gtwapi.Gateway{}
	Gateway.Name = "foobar"
	Gateway.Namespace = "default"
	if labels != nil {
		Gateway.Labels = labels
	}
	Gateway.Spec.GatewayClassName = className
	Gateway.Spec.Listeners = []gtwapi.Listener{
		{
			Name:     "proxy",
			Port:     80,
			Protocol: gtwapi.HTTPProtocolType,
		},
	}
	Condition := metav1.Condition{
		Type:    "Accepted",
		Status:  status,
		Message: "An expected message",
		Reason:  "Test",
	}
	Gateway.Status.Conditions = []metav1.Condition{Condition}

	return Gateway
}

func TestGatewayAnalyzer(t *testing.T) {
	ClassName := gtwapi.ObjectName("exists")
	AcceptedStatus := metav1.ConditionTrue
	GatewayClass := BuildGatewayClass(string(ClassName))

	Gateway := BuildGateway(ClassName, AcceptedStatus, nil)
	// Create a Gateway Analyzer instance with the fake client
	scheme := scheme.Scheme

	err := gtwapi.Install(scheme)
	if err != nil {
		t.Error(err)
	}
	err = apiextensionsv1.AddToScheme(scheme)
	if err != nil {
		t.Error(err)
	}
	objects := []runtime.Object{
		&Gateway,
		&GatewayClass,
	}

	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()

	analyzerInstance := GatewayAnalyzer{}
	config := common.Analyzer{
		Client: &kubernetes.Client{
			CtrlClient: fakeClient,
		},
		Context:   context.Background(),
		Namespace: "default",
	}
	analysisResults, err := analyzerInstance.Analyze(config)
	if err != nil {
		t.Error(err)
	}
	assert.Equal(t, len(analysisResults), 0)

}

func TestEmptyConditionsGatewayAnalyzer(t *testing.T) {
	ClassName := gtwapi.ObjectName("exists")
	AcceptedStatus := metav1.ConditionTrue
	GatewayClass := BuildGatewayClass(string(ClassName))

	Gateway := BuildGateway(ClassName, AcceptedStatus, nil)
	// A freshly created Gateway (or one whose controller has not reported yet)
	// has no status conditions; the analyzer must not panic on it.
	Gateway.Status.Conditions = nil
	// Create a Gateway Analyzer instance with the fake client
	scheme := scheme.Scheme

	err := gtwapi.Install(scheme)
	if err != nil {
		t.Error(err)
	}
	err = apiextensionsv1.AddToScheme(scheme)
	if err != nil {
		t.Error(err)
	}
	objects := []runtime.Object{
		&Gateway,
		&GatewayClass,
	}

	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()

	analyzerInstance := GatewayAnalyzer{}
	config := common.Analyzer{
		Client: &kubernetes.Client{
			CtrlClient: fakeClient,
		},
		Context:   context.Background(),
		Namespace: "default",
	}
	analysisResults, err := analyzerInstance.Analyze(config)
	if err != nil {
		t.Error(err)
	}
	assert.Equal(t, len(analysisResults), 0)

}

func TestMissingClassGatewayAnalyzer(t *testing.T) {
	ClassName := gtwapi.ObjectName("non-existed")
	AcceptedStatus := metav1.ConditionTrue
	Gateway := BuildGateway(ClassName, AcceptedStatus, nil)

	// Create a Gateway Analyzer instance with the fake client
	scheme := scheme.Scheme
	err := gtwapi.Install(scheme)
	if err != nil {
		t.Error(err)
	}
	err = apiextensionsv1.AddToScheme(scheme)
	if err != nil {
		t.Error(err)
	}
	objects := []runtime.Object{
		&Gateway,
	}

	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()

	analyzerInstance := GatewayAnalyzer{}
	config := common.Analyzer{
		Client: &kubernetes.Client{
			CtrlClient: fakeClient,
		},
		Context:   context.Background(),
		Namespace: "default",
	}
	analysisResults, err := analyzerInstance.Analyze(config)
	if err != nil {
		t.Error(err)
	}
	assert.Equal(t, len(analysisResults), 1)

}

func TestStatusGatewayAnalyzer(t *testing.T) {
	ClassName := gtwapi.ObjectName("exists")
	AcceptedStatus := metav1.ConditionUnknown
	GatewayClass := BuildGatewayClass(string(ClassName))

	Gateway := BuildGateway(ClassName, AcceptedStatus, nil)

	// Create a Gateway Analyzer instance with the fake client
	scheme := scheme.Scheme
	err := gtwapi.Install(scheme)
	if err != nil {
		t.Error(err)
	}
	err = apiextensionsv1.AddToScheme(scheme)
	if err != nil {
		t.Error(err)
	}
	objects := []runtime.Object{
		&Gateway,
		&GatewayClass,
	}

	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()

	analyzerInstance := GatewayAnalyzer{}
	config := common.Analyzer{
		Client: &kubernetes.Client{
			CtrlClient: fakeClient,
		},
		Context:   context.Background(),
		Namespace: "default",
	}
	analysisResults, err := analyzerInstance.Analyze(config)
	if err != nil {
		t.Error(err)
	}
	var errorFound bool
	want := "Gateway 'default/foobar' is not accepted. Message: 'An expected message'."
	for _, analysis := range analysisResults {
		for _, got := range analysis.Error {
			if want == got.Text {
				errorFound = true
			}
		}
		if errorFound {
			break
		}
	}

	if !errorFound {
		t.Errorf("Expected message, <%v> , not found in Gateway's analysis results", want)
	}
}

func TestGatewayAnalyzerLabelSelectorFiltering(t *testing.T) {
	ClassName := gtwapi.ObjectName("non-existed")
	AcceptedStatus := metav1.ConditionTrue

	Gateway := BuildGateway(ClassName, AcceptedStatus, map[string]string{"app": "gateway"})
	scheme := scheme.Scheme
	err := gtwapi.Install(scheme)
	if err != nil {
		t.Error(err)
	}
	err = apiextensionsv1.AddToScheme(scheme)
	if err != nil {
		t.Error(err)
	}
	objects := []runtime.Object{
		&Gateway,
	}

	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()

	analyzerInstance := GatewayAnalyzer{}
	// without label selector should return 1 result
	config := common.Analyzer{
		Client: &kubernetes.Client{
			CtrlClient: fakeClient,
		},
		Context:   context.Background(),
		Namespace: "default",
	}
	analysisResults, err := analyzerInstance.Analyze(config)
	if err != nil {
		t.Error(err)
	}
	assert.Equal(t, len(analysisResults), 1)

	// with label selector should return 1 result
	config = common.Analyzer{
		Client: &kubernetes.Client{
			CtrlClient: fakeClient,
		},
		Context:       context.Background(),
		Namespace:     "default",
		LabelSelector: "app=gateway",
	}
	analysisResults, err = analyzerInstance.Analyze(config)
	if err != nil {
		t.Error(err)
	}
	assert.Equal(t, len(analysisResults), 1)

	// with wrong label selector should return 0 result
	config = common.Analyzer{
		Client: &kubernetes.Client{
			CtrlClient: fakeClient,
		},
		Context:       context.Background(),
		Namespace:     "default",
		LabelSelector: "app=wrong",
	}
	analysisResults, err = analyzerInstance.Analyze(config)
	if err != nil {
		t.Error(err)
	}
	assert.Equal(t, len(analysisResults), 0)

}
