package controller

import (
	"testing"
	"time"

	"github.com/gravitl/netmaker/database"
	"github.com/gravitl/netmaker/logic"
	"github.com/gravitl/netmaker/models"
	"github.com/stretchr/testify/assert"
)

type NetworkValidationTestCase struct {
	testname   string
	network    models.Network
	errMessage string
}

func TestCreateNetwork(t *testing.T) {
	database.InitializeDatabase()
	deleteAllNetworks()

	var network models.Network
	network.NetID = "skynet"
	network.AddressRange = "10.0.0.1/24"
	network.DisplayName = "mynetwork"

	err := logic.CreateNetwork(network)
	assert.Nil(t, err)
}
func TestGetNetwork(t *testing.T) {
	database.InitializeDatabase()
	createNet()

	t.Run("GetExistingNetwork", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		assert.Equal(t, "skynet", network.NetID)
	})
	t.Run("GetNonExistantNetwork", func(t *testing.T) {
		network, err := logic.GetNetwork("doesnotexist")
		assert.EqualError(t, err, "no result found")
		assert.Equal(t, "", network.NetID)
	})
}

func TestDeleteNetwork(t *testing.T) {
	database.InitializeDatabase()
	createNet()
	//create nodes
	t.Run("NetworkwithNodes", func(t *testing.T) {
	})
	t.Run("DeleteExistingNetwork", func(t *testing.T) {
		err := logic.DeleteNetwork("skynet")
		assert.Nil(t, err)
	})
	t.Run("NonExistantNetwork", func(t *testing.T) {
		err := logic.DeleteNetwork("skynet")
		assert.Nil(t, err)
	})
}

func TestKeyUpdate(t *testing.T) {
	t.Skip() //test is failing on last assert  --- not sure why
	database.InitializeDatabase()
	createNet()
	existing, err := logic.GetNetwork("skynet")
	assert.Nil(t, err)
	time.Sleep(time.Second * 1)
	network, err := logic.KeyUpdate("skynet")
	assert.Nil(t, err)
	network, err = logic.GetNetwork("skynet")
	assert.Nil(t, err)
	assert.Greater(t, network.KeyUpdateTimeStamp, existing.KeyUpdateTimeStamp)
}

func TestCreateKey(t *testing.T) {
	database.InitializeDatabase()
	createNet()
	keys, _ := logic.GetKeys("skynet")
	for _, key := range keys {
		logic.DeleteKey(key.Name, "skynet")
	}
	var accesskey models.AccessKey
	var network models.Network
	network.NetID = "skynet"
	t.Run("NameTooLong", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		accesskey.Name = "Thisisareallylongkeynamethatwillfail"
		_, err = logic.CreateAccessKey(accesskey, network)
		assert.NotNil(t, err)
		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
	})
	t.Run("BlankName", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		accesskey.Name = ""
		key, err := logic.CreateAccessKey(accesskey, network)
		assert.Nil(t, err)
		assert.NotEqual(t, "", key.Name)
	})
	t.Run("InvalidValue", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		accesskey.Value = "bad-value"
		_, err = logic.CreateAccessKey(accesskey, network)
		assert.NotNil(t, err)
		assert.Contains(t, err.Error(), "Field validation for 'Value' failed on the 'alphanum' tag")
	})
	t.Run("BlankValue", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		accesskey.Name = "mykey"
		accesskey.Value = ""
		key, err := logic.CreateAccessKey(accesskey, network)
		assert.Nil(t, err)
		assert.NotEqual(t, "", key.Value)
		assert.Equal(t, accesskey.Name, key.Name)
	})
	t.Run("ValueTooLong", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		accesskey.Name = "keyname"
		accesskey.Value = "AccessKeyValuethatistoolong"
		_, err = logic.CreateAccessKey(accesskey, network)
		assert.NotNil(t, err)
		assert.Contains(t, err.Error(), "Field validation for 'Value' failed on the 'max' tag")
	})
	t.Run("BlankUses", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		accesskey.Uses = 0
		accesskey.Value = ""
		key, err := logic.CreateAccessKey(accesskey, network)
		assert.Nil(t, err)
		assert.Equal(t, 1, key.Uses)
	})
	t.Run("DuplicateKey", func(t *testing.T) {
		network, err := logic.GetNetwork("skynet")
		assert.Nil(t, err)
		accesskey.Name = "mykey"
		_, err = logic.CreateAccessKey(accesskey, network)
		assert.NotNil(t, err)
		assert.EqualError(t, err, "duplicate AccessKey Name")
	})
}

func TestGetKeys(t *testing.T) {
	database.InitializeDatabase()
	deleteAllNetworks()
	createNet()
	network, err := logic.GetNetwork("skynet")
	assert.Nil(t, err)
	var key models.AccessKey
	key.Name = "mykey"
	_, err = logic.CreateAccessKey(key, network)
	assert.Nil(t, err)
	t.Run("KeyExists", func(t *testing.T) {
		keys, err := logic.GetKeys(network.NetID)
		assert.Nil(t, err)
		assert.NotEqual(t, models.AccessKey{}, keys)
	})
	t.Run("NonExistantKey", func(t *testing.T) {
		err := logic.DeleteKey("mykey", "skynet")
		assert.Nil(t, err)
		keys, err := logic.GetKeys(network.NetID)
		assert.Nil(t, err)
		assert.Equal(t, []models.AccessKey(nil), keys)
	})
}
func TestDeleteKey(t *testing.T) {
	database.InitializeDatabase()
	createNet()
	network, err := logic.GetNetwork("skynet")
	assert.Nil(t, err)
	var key models.AccessKey
	key.Name = "mykey"
	_, err = logic.CreateAccessKey(key, network)
	assert.Nil(t, err)
	t.Run("ExistingKey", func(t *testing.T) {
		err := logic.DeleteKey("mykey", "skynet")
		assert.Nil(t, err)
	})
	t.Run("NonExistantKey", func(t *testing.T) {
		err := logic.DeleteKey("mykey", "skynet")
		assert.NotNil(t, err)
		assert.Equal(t, "key mykey does not exist", err.Error())
	})
}

func TestSecurityCheck(t *testing.T) {
	//these seem to work but not sure it the tests are really testing the functionality

	database.InitializeDatabase()
	t.Run("NoNetwork", func(t *testing.T) {
		err, networks, username := SecurityCheck(false, "", "Bearer secretkey")
		assert.Nil(t, err)
		t.Log(networks, username)
	})
	t.Run("WithNetwork", func(t *testing.T) {
		err, networks, username := SecurityCheck(false, "skynet", "Bearer secretkey")
		assert.Nil(t, err)
		t.Log(networks, username)
	})
	t.Run("BadNet", func(t *testing.T) {
		t.Skip()
		err, networks, username := SecurityCheck(false, "badnet", "Bearer secretkey")
		assert.NotNil(t, err)
		t.Log(err)
		t.Log(networks, username)
	})
	t.Run("BadToken", func(t *testing.T) {
		err, networks, username := SecurityCheck(false, "skynet", "Bearer badkey")
		assert.NotNil(t, err)
		t.Log(err)
		t.Log(networks, username)
	})
}

func TestValidateNetworkUpdate(t *testing.T) {
	t.Skip()
	//This functions is not called by anyone
	//it panics as validation function 'display_name_valid' is not defined
	database.InitializeDatabase()
	//yes := true
	//no := false
	//deleteNet(t)

	//DeleteNetworks
	cases := []NetworkValidationTestCase{
		{
			testname: "InvalidAddress",
			network: models.Network{
				AddressRange: "10.0.0.256",
			},
			errMessage: "Field validation for 'AddressRange' failed on the 'cidr' tag",
		},
		{
			testname: "InvalidAddress6",
			network: models.Network{
				AddressRange6: "2607::ag",
			},
			errMessage: "Field validation for 'AddressRange6' failed on the 'cidr' tag",
		},

		{
			testname: "BadDisplayName",
			network: models.Network{
				DisplayName: "skynet*",
			},
			errMessage: "Field validation for 'DisplayName' failed on the 'alphanum' tag",
		},
		{
			testname: "DisplayNameTooLong",
			network: models.Network{
				DisplayName: "Thisisareallylongdisplaynamethatistoolong",
			},
			errMessage: "Field validation for 'DisplayName' failed on the 'max' tag",
		},
		{
			testname: "DisplayNameTooShort",
			network: models.Network{
				DisplayName: "1",
			},
			errMessage: "Field validation for 'DisplayName' failed on the 'min' tag",
		},
		{
			testname: "InvalidNetID",
			network: models.Network{
				NetID: "contains spaces",
			},
			errMessage: "Field validation for 'NetID' failed on the 'alphanum' tag",
		},
		{
			testname: "NetIDTooLong",
			network: models.Network{
				NetID: "LongNetIDName",
			},
			errMessage: "Field validation for 'NetID' failed on the 'max' tag",
		},
		{
			testname: "ListenPortTooLow",
			network: models.Network{
				DefaultListenPort: 1023,
			},
			errMessage: "Field validation for 'DefaultListenPort' failed on the 'min' tag",
		},
		{
			testname: "ListenPortTooHigh",
			network: models.Network{
				DefaultListenPort: 65536,
			},
			errMessage: "Field validation for 'DefaultListenPort' failed on the 'max' tag",
		},
		{
			testname: "KeepAliveTooBig",
			network: models.Network{
				DefaultKeepalive: 1010,
			},
			errMessage: "Field validation for 'DefaultKeepalive' failed on the 'max' tag",
		},
		{
			testname: "InvalidLocalRange",
			network: models.Network{
				LocalRange: "192.168.0.1",
			},
			errMessage: "Field validation for 'LocalRange' failed on the 'cidr' tag",
		},
		{
			testname: "CheckInIntervalTooBig",
			network: models.Network{
				DefaultCheckInInterval: 100001,
			},
			errMessage: "Field validation for 'DefaultCheckInInterval' failed on the 'max' tag",
		},
		{
			testname: "CheckInIntervalTooSmall",
			network: models.Network{
				DefaultCheckInInterval: 1,
			},
			errMessage: "Field validation for 'DefaultCheckInInterval' failed on the 'min' tag",
		},
	}
	for _, tc := range cases {
		t.Run(tc.testname, func(t *testing.T) {
			network := models.Network(tc.network)
			err := logic.ValidateNetworkUpdate(network)
			assert.NotNil(t, err)
			assert.Contains(t, err.Error(), tc.errMessage)
		})
	}
}

func deleteAllNetworks() {
	deleteAllNodes()
	nets, _ := logic.GetNetworks()
	for _, net := range nets {
		logic.DeleteNetwork(net.NetID)
	}
}

func createNet() {
	var network models.Network
	network.NetID = "skynet"
	network.AddressRange = "10.0.0.1/24"
	network.DisplayName = "mynetwork"
	_, err := logic.GetNetwork("skynet")
	if err != nil {
		logic.CreateNetwork(network)
	}
}
