package jsonpatch_test

import (
	"encoding/json"
	"testing"

	jp "github.com/evanphx/json-patch"
	"github.com/stretchr/testify/assert"
	"gomodules.xyz/jsonpatch/v2"
)

var simpleA = `{"a":100, "b":200, "c":"hello"}`
var simpleB = `{"a":100, "b":200, "c":"goodbye"}`
var simpleC = `{"a":100, "b":100, "c":"hello"}`
var simpleD = `{"a":100, "b":200, "c":"hello", "d":"foo"}`
var simpleE = `{"a":100, "b":200}`
var simplef = `{"a":100, "b":100, "d":"foo"}`
var simpleG = `{"a":100, "b":null, "d":"foo"}`
var empty = `{}`

var arraySrc = `
{
  "spec": {
    "loadBalancerSourceRanges": [
      "192.101.0.0/16",
      "192.0.0.0/24"
    ]
  }
}
`

var arrayDst = `
{
  "spec": {
    "loadBalancerSourceRanges": [
      "192.101.0.0/24"
    ]
  }
}
`

var complexBase = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"} ], "e":{"f":200, "g":"h", "i":"j"}}`
var complexA = `{"a":100, "b":[{"c1":"goodbye", "d1":"foo"},{"c2":"hello2", "d2":"foo2"} ], "e":{"f":200, "g":"h", "i":"j"}}`
var complexB = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"} ], "e":{"f":100, "g":"h", "i":"j"}}`
var complexC = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"} ], "e":{"f":200, "g":"h", "i":"j"}, "k":[{"l":"m"}, {"l":"o"}]}`
var complexD = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"}, {"c3":"hello3", "d3":"foo3"} ], "e":{"f":200, "g":"h", "i":"j"}}`
var complexE = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"} ], "e":{"f":200, "g":"h", "i":"j"}}`

var point = `{"type":"Point", "coordinates":[0.0, 1.0]}`
var lineString = `{"type":"LineString", "coordinates":[[0.0, 1.0], [2.0, 3.0]]}`

var hyperComplexBase = `
{
	"goods": [
	{
		"id": "0001",
		"type": "donut",
		"name": "Cake",
		"ppu": 0.55,
		"batters":
			{
				"batter":
					[
						{ "id": "1001", "type": "Regular" },
						{ "id": "1002", "type": "Chocolate" },
						{ "id": "1003", "type": "Blueberry" },
						{ "id": "1004", "type": "Devil's Food" }
					]
			},
		"topping":
			[
				{ "id": "5001", "type": "None" },
				{ "id": "5002", "type": "Glazed" },
				{ "id": "5005", "type": "Sugar" },
				{ "id": "5007", "type": "Powdered Sugar" },
				{ "id": "5006", "type": "Chocolate with Sprinkles" },
				{ "id": "5003", "type": "Chocolate" },
				{ "id": "5004", "type": "Maple" }
			]
	},
	{
		"id": "0002",
		"type": "donut",
		"name": "Raised",
		"ppu": 0.55,
		"batters":
			{
				"batter":
					[
						{ "id": "1001", "type": "Regular" }
					]
			},
		"topping":
			[
				{ "id": "5001", "type": "None" },
				{ "id": "5002", "type": "Glazed" },
				{ "id": "5005", "type": "Sugar" },
				{ "id": "5003", "type": "Chocolate" },
				{ "id": "5004", "type": "Maple" }
			]
	},
	{
		"id": "0003",
		"type": "donut",
		"name": "Old Fashioned",
		"ppu": 0.55,
		"batters":
			{
				"batter":
					[
						{ "id": "1001", "type": "Regular" },
						{ "id": "1002", "type": "Chocolate" }
					]
			},
		"topping":
			[
				{ "id": "5001", "type": "None" },
				{ "id": "5002", "type": "Glazed" },
				{ "id": "5003", "type": "Chocolate" },
				{ "id": "5004", "type": "Maple" }
			]
	}
]
}`

var hyperComplexA = `
{
	"goods": [
	{
		"id": "0001",
		"type": "donut",
		"name": "Cake",
		"ppu": 0.55,
		"batters":
			{
				"batter":
					[
						{ "id": "1001", "type": "Regular" },
						{ "id": "1002", "type": "Chocolate" },
						{ "id": "1003", "type": "Strawberry" },
						{ "id": "1004", "type": "Devil's Food" }
					]
			},
		"topping":
			[
				{ "id": "5001", "type": "None" },
				{ "id": "5002", "type": "Glazed" },
				{ "id": "5005", "type": "Sugar" },
				{ "id": "5007", "type": "Powdered Sugar" },
				{ "id": "5006", "type": "Chocolate with Sprinkles" },
				{ "id": "5003", "type": "Chocolate" },
				{ "id": "5004", "type": "Maple" }
			]
	},
	{
		"id": "0002",
		"type": "donut",
		"name": "Raised",
		"ppu": 0.55,
		"batters":
			{
				"batter":
					[
						{ "id": "1001", "type": "Regular" }
					]
			},
		"topping":
			[
				{ "id": "5001", "type": "None" },
				{ "id": "5002", "type": "Glazed" },
				{ "id": "5005", "type": "Sugar" },
				{ "id": "5003", "type": "Chocolate" },
				{ "id": "5004", "type": "Maple" }
			]
	},
	{
		"id": "0003",
		"type": "donut",
		"name": "Old Fashioned",
		"ppu": 0.55,
		"batters":
			{
				"batter":
					[
						{ "id": "1001", "type": "Regular" },
						{ "id": "1002", "type": "Chocolate" },
						{ "id": "1003", "type": "Vanilla" }
					]
			},
		"topping":
			[
				{ "id": "5001", "type": "None" },
				{ "id": "5002", "type": "Glazed" },
				{ "id": "5004", "type": "Maple" }
			]
	}
]
}`

var superComplexBase = `
{
	"annotations": {
		"annotation": [
			{
				"name": "version",
				"value": "8"
			},
			{
				"name": "versionTag",
				"value": "Published on May 13, 2015 at 8:48pm (MST)"
			}
		]
	},
	"attributes": {
		"attribute-key": [
			{
				"id": "3b05c943-d81a-436f-b242-8b519e7a6f30",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "d794c7ee-2a4b-4da4-bba7-e8b973d50c4b",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "a0259458-517c-480f-9f04-9b54b1b2af1f",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "9415f39d-c396-4458-9019-fc076c847964",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "0a2e49a9-8989-42fb-97da-cc66334f828b",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "27f5f14a-ea97-4feb-b22a-6ff754a31212",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "6f810508-4615-4fd0-9e87-80f9c94f9ad8",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "3451b1b2-7365-455c-8bb1-0b464d4d3ba1",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "a82ec957-8c26-41ea-8af6-6dd75c384801",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "736c5496-9a6e-4a82-aa00-456725796432",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2d428b3c-9d3b-4ec1-bf98-e00673599d60",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "68566ebb-811d-4337-aba9-a8a8baf90e4b",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "ca88bab1-a1ea-40cc-8f96-96d1e9f1217d",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "c63a12c8-542d-47f3-bee1-30b5fe2b0690",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "cbd9e3bc-6a49-432a-a906-b1674c1de24c",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "03262f07-8a15-416d-a3f5-e2bf561c78f9",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "e5c93b87-83fc-45b6-b4d5-bf1e3f523075",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "72260ac5-3d51-49d7-bb31-f794dd129f1c",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "d856bde1-1b42-4935-9bee-c37e886c9ecf",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "62380509-bedf-4134-95c3-77ff377a4a6a",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "f4ed5ac9-b386-49a6-a0a0-6f3341ce9021",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "528d2bd2-87fe-4a49-954a-c93a03256929",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "ff8951f1-61a7-416b-9223-fac4bb6dac50",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "95c2b011-d782-4042-8a07-6aa4a5765c2e",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "dbe5837b-0624-4a05-91f3-67b5bd9b812a",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "13f198ed-82ab-4e51-8144-bfaa5bf77fd5",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "025312eb-12b6-47e6-9750-0fb31ddc2111",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "24292d58-db66-4ef3-8f4f-005d7b719433",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "22e5b5c4-821c-413a-a5b1-ab866d9a03bb",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2fde0aac-df89-403d-998e-854b949c7b57",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "8b576876-5c16-4178-805e-24984c24fac3",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "415b7d2a-b362-4f1e-b83a-927802328ecb",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "8ef24fc2-ab25-4f22-9d9f-61902b49dc01",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2299b09e-9f8e-4b79-a55c-a7edacde2c85",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "bf506538-f438-425c-be85-5aa2f9b075b8",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2b501dc6-799d-4675-9144-fac77c50c57c",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "c0446da1-e069-417e-bd5a-34edcd028edc",
				"properties": {
					"visible": true
				}
			}
		]
	}
}`

var superComplexA = `
{
	"annotations": {
		"annotation": [
			{
				"name": "version",
				"value": "8"
			},
			{
				"name": "versionTag",
				"value": "Published on May 13, 2015 at 8:48pm (MST)"
			}
		]
	},
	"attributes": {
		"attribute-key": [
			{
				"id": "3b05c943-d81a-436f-b242-8b519e7a6f30",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "d794c7ee-2a4b-4da4-bba7-e8b973d50c4b",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "a0259458-517c-480f-9f04-9b54b1b2af1f",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "9415f39d-c396-4458-9019-fc076c847964",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "0a2e49a9-8989-42fb-97da-cc66334f828b",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "27f5f14a-ea97-4feb-b22a-6ff754a31212",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "6f810508-4615-4fd0-9e87-80f9c94f9ad8",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "3451b1b2-7365-455c-8bb1-0b464d4d3ba1",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "a82ec957-8c26-41ea-8af6-6dd75c384801",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "736c5496-9a6e-4a82-aa00-456725796432",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2d428b3c-9d3b-4ec1-bf98-e00673599d60",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "68566ebb-811d-4337-aba9-a8a8baf90e4b",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "ca88bab1-a1ea-40cc-8f96-96d1e9f1217d",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "c63a12c8-542d-47f3-bee1-30b5fe2b0690",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "cbd9e3bc-6a49-432a-a906-b1674c1de24c",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "03262f07-8a15-416d-a3f5-e2bf561c78f9",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "e5c93b87-83fc-45b6-b4d5-bf1e3f523075",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "72260ac5-3d51-49d7-bb31-f794dd129f1c",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "d856bde1-1b42-4935-9bee-c37e886c9ecf",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "62380509-bedf-4134-95c3-77ff377a4a6a",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "f4ed5ac9-b386-49a6-a0a0-6f3341ce9021",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "528d2bd2-87fe-4a49-954a-c93a03256929",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "ff8951f1-61a7-416b-9223-fac4bb6dac50",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "95c2b011-d782-4042-8a07-6aa4a5765c2e",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "dbe5837b-0624-4a05-91f3-67b5bd9b812a",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "13f198ed-82ab-4e51-8144-bfaa5bf77fd5",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "025312eb-12b6-47e6-9750-0fb31ddc2111",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "24292d58-db66-4ef3-8f4f-005d7b719433",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "22e5b5c4-821c-413a-a5b1-ab866d9a03bb",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2fde0aac-df89-403d-998e-854b949c7b57",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "8b576876-5c16-4178-805e-24984c24fac3",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "415b7d2a-b362-4f1e-b83a-927802328ecb",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "8ef24fc2-ab25-4f22-9d9f-61902b49dc01",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2299b09e-9f8e-4b79-a55c-a7edacde2c85",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "bf506538-f438-425c-be85-5aa2f9b075b8",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "2b501dc6-799d-4675-9144-fac77c50c57c",
				"properties": {
					"visible": true
				}
			},
			{
				"id": "c0446da1-e069-417e-bd5a-34edcd028edc",
				"properties": {
					"visible": false
				}
			}
		]
	}
}`

var (
	oldDeployment = `{
  "apiVersion": "apps/v1beta1",
  "kind": "Deployment",
  "metadata": {
    "annotations": {
      "k8s.io/app": "busy-dep"
    }
  }
}`

	newDeployment = `{
  "apiVersion": "apps/v1beta1",
  "kind": "Deployment",
  "metadata": {
    "annotations": {
      "k8s.io/app": "busy-dep",
      "docker.com/commit": "github.com/myrepo#xyz"
    }
  }
}`
)

var (
	oldNestedObj = `{
  "apiVersion": "kubedb.com/v1alpha1",
  "kind": "Elasticsearch",
  "metadata": {
    "name": "quick-elasticsearch",
    "namespace": "demo"
  },
  "spec": {
    "doNotPause": true,
    "version": "5.6"
  }
}`

	newNestedObj = `{
  "apiVersion": "kubedb.com/v1alpha1",
  "kind": "Elasticsearch",
  "metadata": {
    "name": "quick-elasticsearch",
    "namespace": "demo"
  },
  "spec": {
    "doNotPause": true,
    "version": "5.6",
    "storageType": "Durable",
    "updateStrategy": {
      "type": "RollingUpdate"
    },
    "terminationPolicy": "Pause"
  }
}`
)

var (
	oldArray = `{
  "apiVersion": "kubedb.com/v1alpha1",
  "kind": "Elasticsearch",
  "metadata": {
    "name": "quick-elasticsearch",
    "namespace": "demo"
  },
  "spec": {
    "tolerations": [
      {
          "key": "node.kubernetes.io/key1",
          "operator": "Equal",
          "value": "value1",
          "effect": "NoSchedule"
      },
      {
          "key": "node.kubernetes.io/key2",
          "operator": "Equal",
          "value": "value2",
          "effect": "NoSchedule"
      },
      {
          "key": "node.kubernetes.io/not-ready",
          "operator": "Exists",
          "effect": "NoExecute",
          "tolerationSeconds": 300
      },
      {
          "key": "node.kubernetes.io/unreachable",
          "operator": "Exists",
          "effect": "NoExecute",
          "tolerationSeconds": 300
      }
    ]
  }
}`

	newArray = `{
  "apiVersion": "kubedb.com/v1alpha1",
  "kind": "Elasticsearch",
  "metadata": {
    "name": "quick-elasticsearch",
    "namespace": "demo"
  },
  "spec": {
    "tolerations": [
      {
          "key": "node.kubernetes.io/key2",
          "operator": "Equal",
          "value": "value2",
          "effect": "NoSchedule"
      },
      {
          "key": "node.kubernetes.io/key1",
          "operator": "Equal",
          "value": "value1",
          "effect": "NoSchedule"
      }
    ]
  }
}`
)

var (
	nullKeyA = `{
  "apiVersion": "cert-manager.io/v1",
  "kind": "CertificateRequest",
  "metadata": {
    "creationTimestamp": null,
    "name": "test-cr",
    "namespace": "default-unit-test-ns"
  },
  "spec": {
    "issuerRef": {
      "name": ""
    },
    "request": null
  },
  "status": {}
}`
	nullKeyB = `{
  "apiVersion": "cert-manager.io/v1",
  "kind": "CertificateRequest",
  "metadata": {
    "creationTimestamp": null,
    "name": "test-cr",
    "namespace": "default-unit-test-ns"
  },
  "spec": {
    "issuerRef": {
      "name": ""
    },
    "request": "bXV0YXRpb24gY2FsbGVk"
  },
  "status": {}
}`
)

var (
	emptyKeyA = `{"":[0]}`
	emptyKeyB = `{"":[]}`
)

var (
	specialChars = string([]byte{123, 34, 92, 98, 34, 58, 91, 93, 125})
)

func TestCreatePatch(t *testing.T) {
	cases := []struct {
		name string
		src  string
		dst  string
	}{
		// simple
		{"Simple:OneNullReplace", simplef, simpleG},
		{"Simple:Same", simpleA, simpleA},
		{"Simple:OneStringReplace", simpleA, simpleB},
		{"Simple:OneIntReplace", simpleA, simpleC},
		{"Simple:OneAdd", simpleA, simpleD},
		{"Simple:OneRemove", simpleA, simpleE},
		{"Simple:VsEmpty", simpleA, empty},
		// array types
		{"Array:Same", arraySrc, arraySrc},
		{"Array:BoolReplace", arraySrc, arrayDst},
		{"Array:AlmostSame", `{"Lines":[1,2,3,4,5,6,7,8,9,10]}`, `{"Lines":[2,3,4,5,6,7,8,9,10,11]}`},
		{"Array:Remove", `{"x":["A", "B", "C"]}`, `{"x":["D"]}`},
		{"Array:EditDistance", `{"letters":["A","B","C","D","E","F","G","H","I","J","K"]}`, `{"letters":["L","M","N"]}`},
		// complex types
		{"Complex:Same", complexBase, complexBase},
		{"Complex:OneStringReplaceInArray", complexBase, complexA},
		{"Complex:OneIntReplace", complexBase, complexB},
		{"Complex:OneAdd", complexBase, complexC},
		{"Complex:OneAddToArray", complexBase, complexC},
		{"Complex:VsEmpty", complexBase, empty},
		// geojson
		{"GeoJson:PointLineStringReplace", point, lineString},
		{"GeoJson:LineStringPointReplace", lineString, point},
		// HyperComplex
		{"HyperComplex:Same", hyperComplexBase, hyperComplexBase},
		{"HyperComplex:BoolReplace", hyperComplexBase, hyperComplexA},
		// SuperComplex
		{"SuperComplex:Same", superComplexBase, superComplexBase},
		{"SuperComplex:BoolReplace", superComplexBase, superComplexA},
		// map
		{"Kubernetes:Annotations", oldDeployment, newDeployment},
		// crd with nested object
		{"Nested Member Object", oldNestedObj, newNestedObj},
		// array with different order
		{"Different Array", oldArray, newArray},
		{"Array at root", `[{"asdf":"qwerty"}]`, `[{"asdf":"bla"},{"asdf":"zzz"}]`},
		{"Empty array at root", `[]`, `[{"asdf":"bla"},{"asdf":"zzz"}]`},
		{"Null Key uses replace operation", nullKeyA, nullKeyB},
		// empty key
		{"Empty key", emptyKeyA, emptyKeyB},
		// special chars
		{"Special chars", empty, specialChars},
	}

	for _, c := range cases {
		t.Run(c.name+"[src->dst]", func(t *testing.T) {
			check(t, c.src, c.dst)
		})
		t.Run(c.name+"[dst->src]", func(t *testing.T) {
			check(t, c.dst, c.src)
		})
	}
}

func check(t *testing.T, src, dst string) {
	patch, err := jsonpatch.CreatePatch([]byte(src), []byte(dst))
	assert.Nil(t, err)

	data, err := json.Marshal(patch)
	assert.Nil(t, err)

	p2, err := jp.DecodePatch(data)
	assert.Nil(t, err)

	d2, err := p2.Apply([]byte(src))
	assert.Nil(t, err)

	assert.JSONEq(t, dst, string(d2))
}
