package config

import (
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"

	"edge-infra.dev/pkg/sds/display/constants"
)

var (
	hostname = "test"

	xorgConf        = "Section \"Screen\""
	updatedXorgConf = "Section \"Monitor\""

	openboxConf        = "<openbox_config></openbox_config>"
	updatedOpenboxConf = "<openbox_config><theme></theme></openbox_config>"

	enabled  = true
	disabled = false
)

func TestDeepCopy(t *testing.T) {
	src := &Config{
		Hostname:          hostname,
		xorgConf:          &updatedXorgConf,
		openboxConf:       &updatedOpenboxConf,
		cursorEnabled:     &enabled,
		displayctlEnabled: &disabled,
	}

	dst := src.DeepCopy()

	// check pointers are for new addresses
	assert.NotSame(t, src, dst)
	assert.NotSame(t, src.xorgConf, dst.xorgConf)
	assert.NotSame(t, src.openboxConf, dst.openboxConf)
	assert.NotSame(t, src.cursorEnabled, dst.cursorEnabled)
	assert.NotSame(t, src.displayctlEnabled, dst.displayctlEnabled)

	// check values were copied
	assert.Equal(t, src.Hostname, dst.Hostname)
	assert.Equal(t, src.xorgConf, dst.xorgConf)
	assert.Equal(t, src.openboxConf, dst.openboxConf)
	assert.Equal(t, src.cursorEnabled, dst.cursorEnabled)
	assert.Equal(t, src.displayctlEnabled, dst.displayctlEnabled)
}

func TestDeepCopyInto(t *testing.T) {
	src := &Config{
		Hostname:      hostname,
		xorgConf:      &updatedXorgConf,
		cursorEnabled: &enabled,
	}

	dst := &Config{
		Hostname:          "old",
		xorgConf:          ptr.To("old"),
		cursorEnabled:     ptr.To(false),
		displayctlEnabled: ptr.To(false),
	}

	src.DeepCopyInto(dst)

	// check pointers are for new addresses
	assert.NotSame(t, src, dst)
	assert.NotSame(t, src.xorgConf, dst.xorgConf)
	assert.NotSame(t, src.cursorEnabled, dst.cursorEnabled)

	// check nil pointers are the same
	assert.Same(t, src.openboxConf, dst.openboxConf)
	assert.Same(t, src.displayctlEnabled, dst.displayctlEnabled)

	// check values were copied
	assert.Equal(t, src.Hostname, dst.Hostname)
	assert.Equal(t, src.xorgConf, dst.xorgConf)
	assert.Equal(t, src.cursorEnabled, dst.cursorEnabled)
}

func TestEqual(t *testing.T) {
	assert.True(t, Equal(
		nil,
		nil,
	))
	assert.True(t, Equal(
		&Config{},
		&Config{},
	))
	assert.True(t, Equal(
		&Config{xorgConf: &xorgConf, openboxConf: &openboxConf, cursorEnabled: &enabled, displayctlEnabled: &disabled},
		&Config{xorgConf: &xorgConf, openboxConf: &openboxConf, cursorEnabled: &enabled, displayctlEnabled: &disabled},
	))

	assert.False(t, Equal(
		nil,
		&Config{},
	))
	assert.False(t, Equal(
		&Config{xorgConf: &xorgConf},
		nil,
	))
	assert.False(t, Equal(
		&Config{xorgConf: &xorgConf},
		&Config{xorgConf: &updatedXorgConf},
	))
	assert.False(t, Equal(
		&Config{xorgConf: &xorgConf, openboxConf: &openboxConf, cursorEnabled: &enabled, displayctlEnabled: &disabled},
		&Config{xorgConf: &xorgConf, openboxConf: &updatedOpenboxConf, cursorEnabled: &enabled, displayctlEnabled: &disabled},
	))
	assert.False(t, Equal(
		&Config{xorgConf: &xorgConf, openboxConf: &openboxConf, cursorEnabled: &enabled, displayctlEnabled: &disabled},
		&Config{xorgConf: &xorgConf, openboxConf: &openboxConf, cursorEnabled: &disabled, displayctlEnabled: &disabled},
	))
	assert.False(t, Equal(
		&Config{xorgConf: &xorgConf, openboxConf: &openboxConf, cursorEnabled: &enabled, displayctlEnabled: &enabled},
		&Config{xorgConf: &xorgConf, openboxConf: &openboxConf, cursorEnabled: &disabled, displayctlEnabled: &disabled},
	))
}

func TestXorgConf(t *testing.T) {
	// xorg config should be empty by default
	config := &Config{}
	assert.Equal(t, "", config.XorgConf())

	// xorg config should return value when set
	config.SetXorgConf(xorgConf)
	assert.Equal(t, xorgConf, config.XorgConf())
}

func TestOpenboxConf(t *testing.T) {
	// openbox config should be empty by default
	config := &Config{}
	assert.Equal(t, "", config.OpenboxConf())

	// openbox config should return value when set
	config.SetOpenboxConf(openboxConf)
	assert.Equal(t, openboxConf, config.OpenboxConf())
}

func TestCursor(t *testing.T) {
	// cursor should be disabled by default
	config := &Config{}
	assert.False(t, config.CursorEnabled())

	// cursor should be disabled if set in config
	config.SetCursorEnabled(false)
	assert.False(t, config.CursorEnabled())

	// cursor should be enabled if set in config
	config.SetCursorEnabled(true)
	assert.True(t, config.CursorEnabled())
}

func TestDisplayctlEnabled(t *testing.T) {
	// dispalyctl should be enabled by default
	config := &Config{}
	assert.True(t, config.DisplayctlEnabled())

	// displayctl should be disabled if Xorg config is provided
	// but displayctl enabled is not specified
	config.SetXorgConf(xorgConf)
	assert.False(t, config.DisplayctlEnabled())

	// displayctl should be enabled if Xorg config is provided
	// and displayctl is enabled
	config.SetDisplayctlEnabled(true)
	assert.True(t, config.DisplayctlEnabled())
}

func TestToConfigMap(t *testing.T) {
	config := &Config{
		Hostname: hostname,
	}

	// empty config
	assert.Equal(
		t,
		&corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: constants.Namespace,
				Name:      "xserver-config-test",
			},
			Data: map[string]string{},
		},
		config.ToConfigMap(),
	)

	// only Xorg config configured
	config.SetXorgConf(xorgConf)
	assert.Equal(
		t,
		&corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: constants.Namespace,
				Name:      "xserver-config-test",
			},
			Data: map[string]string{
				xorgConfKey: xorgConf,
			},
		},
		config.ToConfigMap(),
	)

	// all fields configured
	config.SetOpenboxConf(openboxConf)
	config.SetCursorEnabled(true)
	config.SetDisplayctlEnabled(true)
	assert.Equal(
		t,
		&corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: constants.Namespace,
				Name:      "xserver-config-test",
			},
			Data: map[string]string{
				xorgConfKey:          xorgConf,
				openboxConfKey:       openboxConf,
				cursorEnabledKey:     strconv.FormatBool(enabled),
				displayctlEnabledKey: strconv.FormatBool(enabled),
			},
		},
		config.ToConfigMap(),
	)
}

func TestFromConfigMap(t *testing.T) {
	// nil ConfigMap returns error
	config, err := FromConfigMap(nil)
	assert.Nil(t, config)
	assert.ErrorIs(t, err, errXServerConfigMapCannotBeNil)

	// invalid name returns error
	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: constants.Namespace,
			Name:      "xserver-config", // expects xserver-config-HOSTNAME
		},
		Data: map[string]string{},
	}
	config, err = FromConfigMap(configMap)
	assert.Nil(t, config)
	assert.ErrorContains(t, err, errXServerConfigMapNameHasFromFormat.Error())

	// valid name returns config as expected
	configMap.ObjectMeta.Name = ConfigMapNameFromHostname(hostname)
	config, err = FromConfigMap(configMap)
	assert.NoError(t, err)
	assert.Equal(t,
		&Config{
			Hostname: hostname,
		},
		config,
	)

	// update config fields
	configMap.Data[xorgConfKey] = xorgConf
	configMap.Data[openboxConfKey] = openboxConf
	configMap.Data[cursorEnabledKey] = strconv.FormatBool(disabled)
	configMap.Data[displayctlEnabledKey] = strconv.FormatBool(enabled)
	config, err = FromConfigMap(configMap)
	assert.NoError(t, err)
	assert.Equal(t,
		&Config{
			Hostname:          hostname,
			xorgConf:          &xorgConf,
			openboxConf:       &openboxConf,
			cursorEnabled:     &disabled,
			displayctlEnabled: &enabled,
		},
		config,
	)

	// invalid cursor enabled value returns error
	configMap.Data[cursorEnabledKey] = "enabled" // expects true/false
	config, err = FromConfigMap(configMap)
	assert.Nil(t, config)
	assert.ErrorContains(t, err, cursorEnabledKey)
	assert.ErrorContains(t, err, errFlagShouldBeTrueFalse.Error())

	// invalid displayctl enabled value returns error
	configMap.Data[cursorEnabledKey] = strconv.FormatBool(enabled)
	configMap.Data[displayctlEnabledKey] = "enabled" // expects true/false
	config, err = FromConfigMap(configMap)
	assert.Nil(t, config)
	assert.ErrorContains(t, err, displayctlEnabledKey)
	assert.ErrorContains(t, err, errFlagShouldBeTrueFalse.Error())
}