package mutate

import (
	"encoding/json"
	"reflect"
	"strings"
	"testing"

	"gotest.tools/assert"
	"sigs.k8s.io/controller-runtime/pkg/log"
)

func TestMeetConditions_NoAnchor(t *testing.T) {
	overlayRaw := []byte(`
	 {  
		"subsets":[  
		   {  
			  "ports":[  
				 {  
					"name":"secure-connection",
					"port":444,
					"protocol":"UDP"
				 }
			  ]
		   }
		]
	 }`)
	var overlay interface{}

	err := json.Unmarshal(overlayRaw, &overlay)
	assert.Assert(t, reflect.DeepEqual(err, nil))

	_, err = meetConditions(log.Log, nil, overlay)

	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))
}

func TestMeetConditions_conditionalAnchorOnMap(t *testing.T) {
	resourceRaw := []byte(`
	 {  
		"apiVersion":"v1",
		"kind":"Endpoints",
		"metadata":{  
		   "name":"test-endpoint",
		   "labels":{  
			  "label":"test"
		   }
		},
		"subsets":[  
		   {  
			  "addresses":[  
				 {  
					"ip":"192.168.10.171"
				 }
			  ],
			  "ports":[  
				 {  
					"name":"secure-connection",
					"port":443,
					"protocol":"TCP"
				 }
			  ]
		   }
		]
	 }`)

	overlayRaw := []byte(`
	 {  
		"subsets":[  
		   {  
			  "(ports)":[  
				 {  
					"name":"secure-connection",
					"port":444,
					"protocol":"UDP"
				 }
			  ]
		   }
		]
	 }`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRaw, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)
	assert.Assert(t, !reflect.DeepEqual(err, overlayError{}))

	overlayRaw = []byte(`
	{  
	   "(subsets)":[  
		  {  
			 "ports":[  
				{  
				   "name":"secure-connection",
				   "port":443,
				   "(protocol)":"TCP"
				}
			 ]
		  }
	   ]
	}`)

	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, overlayerr := meetConditions(log.Log, resource, overlay)
	assert.Assert(t, reflect.DeepEqual(overlayerr, overlayError{}))
}

func TestMeetConditions_DifferentTypes(t *testing.T) {
	resourceRaw := []byte(`
	{
		"apiVersion": "v1",
		"kind": "Endpoints",
		"metadata": {
		   "name": "test-endpoint"
		},
		"subsets": {
		   "addresses": {
			  "ip": "192.168.10.171"
		   }
		}
	}`)

	overlayRaw := []byte(`
	 {  
		"subsets":[  
		   {  
			  "ports":[  
				 {  
					"(name)":"secure-connection",
					"port":444,
					"protocol":"UDP"
				 }
			  ]
		   }
		]
	 }`)
	var resource, overlay interface{}

	err := json.Unmarshal(resourceRaw, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	// anchor exist

	_, err = meetConditions(log.Log, resource, overlay)
	assert.Assert(t, strings.Contains(err.Error(), "element type mismatch at path /subsets/"))
}

func TestMeetConditions_anchosInSameObject(t *testing.T) {
	resourceRaw := []byte(`
	{  
	   "apiVersion":"v1",
	   "kind":"Endpoints",
	   "metadata":{  
		  "name":"test-endpoint",
		  "labels":{  
			 "label":"test"
		  }
	   },
	   "subsets":[  
		  {  
			 "addresses":[  
				{  
				   "ip":"192.168.10.171"
				}
			 ],
			 "ports":[  
				{  
				   "name":"secure-connection",
				   "port":443,
				   "protocol":"TCP"
				}
			 ]
		  }
	   ]
	}`)

	overlayRaw := []byte(`
	{  
	   "subsets":[  
		  {  
			 "ports":[  
				{  
				   "(name)":"secure-connection",
				   "(port)":444,
				   "protocol":"UDP"
				}
			 ]
		  }
	   ]
	}`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRaw, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)
	assert.Error(t, err, "[overlayError:0] Failed validating value 443 with overlay 444")
}

func TestMeetConditions_anchorOnPeer(t *testing.T) {
	resourceRaw := []byte(`
	 {  
		"apiVersion":"v1",
		"kind":"Endpoints",
		"metadata":{  
		   "name":"test-endpoint",
		   "labels":{  
			  "label":"test"
		   }
		},
		"subsets":[  
		   {  
			  "addresses":[  
				 {  
					"ip":"192.168.10.171"
				 }
			  ],
			  "ports":[  
				 {  
					"name":"secure-connection",
					"port":443,
					"protocol":"TCP"
				 }
			  ]
		   }
		]
	 }`)

	overlayRaw := []byte(`
	 {  
		"subsets":[  
		   {  
			"addresses":[  
				{  
				   "(ip)":"192.168.10.171"
				}
			 ],
			  "ports":[  
				 {  
					"(name)":"secure-connection",
					"port":444,
					"protocol":"UDP"
				 }
			  ]
		   }
		]
	 }`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRaw, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)

	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))
}

func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) {
	overlayRaw := []byte(`{
		"spec": {
			"template": {
				"metadata": {
					"labels": {
						"(app)": "nginx"
					}
				},
				"spec": {
					"containers": [
						{
							"(image)": "*:latest",
							"imagePullPolicy": "IfNotPresent",
							"ports": [
								{
									"containerPort": 8080
								}
							]
						}
					]
				}
			}
		}
	}`)
	resourceRaw := []byte(`{
		"apiVersion": "apps/v1",
		"kind": "Deployment",
		"metadata": {
			"name": "nginx-deployment",
			"labels": {
				"app": "nginx"
			}
		},
		"spec": {
			"replicas": 1,
			"selector": {
				"matchLabels": {
					"app": "nginx"
				}
			},
			"template": {
				"metadata": {
					"labels": {
						"app": "nginx"
					}
				},
				"spec": {
					"containers": [
						{
							"name": "nginx",
							"image": "nginx:latest",
							"ports": [
								{
									"containerPort": 80
								}
							]
						},
						{
							"name": "ghost",
							"image": "ghost:latest"
						}
					]
				}
			}
		}
	}`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRaw, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)

	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))
}

var resourceRawAnchorOnPeers = []byte(`{
	"apiVersion": "apps/v1",
	"kind": "Deployment",
	"metadata": {
	   "name": "psp-demo-unprivileged",
	   "labels": {
		  "app.type": "prod"
	   }
	},
	"spec": {
	   "replicas": 1,
	   "selector": {
		  "matchLabels": {
			 "app": "psp"
		  }
	   },
	   "template": {
		  "metadata": {
			 "labels": {
				"app": "psp"
			 }
		  },
		  "spec": {
			 "securityContext": {
				"runAsNonRoot": true
			 },
			 "containers": [
				{
				   "name": "sec-ctx-unprivileged",
				   "image": "nginxinc/nginx-unprivileged",
				   "securityContext": {
					  "runAsNonRoot": true,
					  "allowPrivilegeEscalation": false
				   },
				   "env": [
					  {
						 "name": "ENV_KEY",
						 "value": "ENV_VALUE"
					  }
				   ]
				}
			 ]
		  }
	   }
	}
 }`)

func TestMeetConditions_anchorsOnPeer_single(t *testing.T) {
	overlayRaw := []byte(`{
		"spec": {
		   "template": {
			  "spec": {
				 "containers": [
					{
					   "(image)": "*/nginx-unprivileged",
					   "securityContext": {
						  "runAsNonRoot": true,
						  "allowPrivilegeEscalation": false
					   },
					   "env": [
						  {
							 "name": "ENV_KEY",
							 "value": "ENV_VALUE"
						  }
					   ]
					}
				 ]
			  }
		   }
		}
	 }`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRawAnchorOnPeers, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)

	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))
}

func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
	overlayRaw := []byte(`{
		"spec": {
		   "template": {
			  "spec": {
				 "containers": [
					{
					   "(image)": "*/nginx-unprivileged",
					   "securityContext": {
						  "(runAsNonRoot)": false,
						  "allowPrivilegeEscalation": false
					   },
					   "env": [
						  {
							 "name": "ENV_KEY",
							 "value": "ENV_VALUE"
						  }
					   ]
					}
				 ]
			  }
		   }
		}
	 }`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRawAnchorOnPeers, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)

	assert.Error(t, err, "[overlayError:0] Failed validating value true with overlay false")

	overlayRaw = []byte(`{
		"spec": {
		   "template": {
			  "spec": {
				 "containers": [
					{
					   "(image)": "*/nginx-unprivileged",
					   "securityContext": {
						  "runAsNonRoot": true,
						  "allowPrivilegeEscalation": false
					   },
					   "env": [
						  {
							 "(name)": "ENV_KEY",
							 "value": "ENV_VALUE"
						  }
					   ]
					}
				 ]
			  }
		   }
		}
	 }`)

	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)
	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))

	overlayRaw = []byte(`{
		"spec": {
		   "template": {
			  "spec": {
				 "containers": [
					{
					   "image": "*/nginx-unprivileged",
					   "securityContext": {
						  "runAsNonRoot": true,
						  "(allowPrivilegeEscalation)": false
					   },
					   "env": [
						  {
							 "(name)": "ENV_KEY",
							 "value": "ENV_VALUE"
						  }
					   ]
					}
				 ]
			  }
		   }
		}
	 }`)

	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)
	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))
}

func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
	overlayRaw := []byte(`{
		"spec": {
		   "template": {
			  "spec": {
				 "containers": [
					{
					   "(image)": "*/nginx-unprivileged",
					   "securityContext": {
						  "(runAsNonRoot)": true,
						  "allowPrivilegeEscalation": false
					   },
					   "env": [
						  {
							 "(name)": "ENV_KEY",
							 "value": "ENV_VALUE"
						  }
					   ]
					}
				 ]
			  }
		   }
		}
	 }`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRawAnchorOnPeers, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)

	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))

	overlayRaw = []byte(`{
		"spec": {
		   "template": {
			  "spec": {
				 "containers": [
					{
					   "(image)": "*/nginx-unprivileged",
					   "securityContext": {
						  "runAsNonRoot": true,
						  "(allowPrivilegeEscalation)": false
					   },
					   "env": [
						  {
							 "(name)": "ENV_KEY",
							 "value": "ENV_VALUE"
						  }
					   ]
					}
				 ]
			  }
		   }
		}
	 }`)

	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)
	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))

	overlayRaw = []byte(`{
		"spec": {
		   "template": {
			  "spec": {
				 "containers": [
					{
					   "(image)": "*/nginx-unprivileged",
					   "securityContext": {
						  "runAsNonRoot": true,
						  "(allowPrivilegeEscalation)": false
					   },
					   "(env)": [
						  {
							 "name": "ENV_KEY",
							 "value": "ENV_VALUE1"
						  }
					   ]
					}
				 ]
			  }
		   }
		}
	 }`)

	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	_, err = meetConditions(log.Log, resource, overlay)
	assert.Error(t, err, "[overlayError:0] Failed validating value ENV_VALUE with overlay ENV_VALUE1")
}

func TestMeetConditions_AtleastOneExist(t *testing.T) {
	overlayRaw := []byte(`
	{
		"metadata": {
		   "annotations": {
			  "+(cluster-autoscaler.kubernetes.io/safe-to-evict)": true
		   }
		},
		"spec": {
		   "volumes": [
			  {
				 "(emptyDir)": {}
			  }
		   ]
		}
	 }`)

	// validate when resource has multiple same blocks
	resourceRaw := []byte(`
	{
		"spec": {
		   "containers": [
			  {
				 "image": "k8s.gcr.io/test-webserver",
				 "name": "test-container",
				 "volumeMounts": [
					{
					   "mountPath": "/cache",
					   "name": "cache-volume"
					}
				 ]
			  }
		   ],
		   "volumes": [
			  {
				 "name": "cache-volume1",
				 "emptyDir": 1
			  },
			  {
				 "name": "cache-volume2",
				 "emptyDir": 2
			  },
			  {
				 "name": "cache-volume3",
				 "emptyDir": {}
			  }
		   ]
		}
	 }`)

	var resource, overlay interface{}

	err := json.Unmarshal(resourceRaw, &resource)
	assert.NilError(t, err)
	err = json.Unmarshal(overlayRaw, &overlay)
	assert.NilError(t, err)

	path, err := meetConditions(log.Log, resource, overlay)

	assert.Assert(t, reflect.DeepEqual(err, overlayError{}))
	assert.Assert(t, len(path) == 0)
}
