/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package merge_test

import (
	"testing"

	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
	. "sigs.k8s.io/structured-merge-diff/v4/internal/fixture"
	"sigs.k8s.io/structured-merge-diff/v4/merge"
	"sigs.k8s.io/structured-merge-diff/v4/typed"
)

var duplicatesParser = func() Parser {
	parser, err := typed.NewParser(`types:
- name: type
  map:
    fields:
      - name: list
        type:
          namedType: associativeList
      - name: unrelated
        type:
          scalar: numeric
      - name: set
        type:
          namedType: set
- name: associativeList
  list:
    elementType:
      namedType: myElement
    elementRelationship: associative
    keys:
    - name
- name: myElement
  map:
    fields:
    - name: name
      type:
        scalar: string
    - name: value1
      type:
        scalar: numeric
    - name: value2
      type:
        scalar: numeric
- name: set
  list:
    elementType:
      scalar: numeric
    elementRelationship: associative
`)
	if err != nil {
		panic(err)
	}
	return SameVersionParser{T: parser.Type("type")}
}()

func TestDuplicates(t *testing.T) {
	tests := map[string]TestCase{
		"sets/ownership/duplicates": {
			Ops: []Operation{
				Update{
					Manager: "updater-one",
					Object: `
						set: [1, 1, 3, 4]
					`,
					APIVersion: "v1",
				},
			},
			Managed: fieldpath.ManagedFields{
				"updater-one": fieldpath.NewVersionedSet(
					_NS(
						_P("set"),
						_P("set", _V(1)),
						_P("set", _V(3)),
						_P("set", _V(4)),
					),
					"v1",
					false,
				),
			},
		},
		"sets/ownership/add_duplicate": {
			Ops: []Operation{
				Update{
					Manager: "updater-one",
					Object: `
						set: [1, 3, 4]
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "updater-two",
					Object: `
						set: [1, 1, 3, 4]
					`,
					APIVersion: "v1",
				},
			},
			Managed: fieldpath.ManagedFields{
				"updater-one": fieldpath.NewVersionedSet(
					_NS(
						_P("set"),
						_P("set", _V(3)),
						_P("set", _V(4)),
					),
					"v1",
					false,
				),
				"updater-two": fieldpath.NewVersionedSet(
					_NS(
						_P("set", _V(1)),
					),
					"v1",
					false,
				),
			},
		},
		"sets/ownership/remove_duplicate": {
			Ops: []Operation{
				Update{
					Manager: "updater-one",
					Object: `
						set: [1, 1, 3, 4]
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "updater-two",
					Object: `
						set: [1, 3, 4]
					`,
					APIVersion: "v1",
				},
			},
			Managed: fieldpath.ManagedFields{
				"updater-one": fieldpath.NewVersionedSet(
					_NS(
						_P("set"),
						_P("set", _V(3)),
						_P("set", _V(4)),
					),
					"v1",
					false,
				),
				"updater-two": fieldpath.NewVersionedSet(
					_NS(
						_P("set", _V(1)),
					),
					"v1",
					false,
				),
			},
		},
		"sets/merging/remove_duplicate": {
			Ops: []Operation{
				Update{
					Manager: "updater",
					Object: `
						set: [1, 1, 3, 4]
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "applier",
					Object: `
						set: [1]
					`,
					APIVersion: "v1",
					Conflicts: merge.Conflicts{
						{Manager: "updater", Path: _P("set", _V(1))},
					},
				},
				ForceApply{
					Manager: "applier",
					Object: `
						set: [1]
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				set: [1, 3, 4]
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("set"),
						_P("set", _V(3)),
						_P("set", _V(4)),
					),
					"v1",
					false,
				),
				"applier": fieldpath.NewVersionedSet(
					_NS(
						_P("set", _V(1)),
					),
					"v1",
					true,
				),
			},
		},
		"sets/merging/ignore_duplicate": {
			Ops: []Operation{
				Update{
					Manager: "updater",
					Object: `
						set: [1, 1, 3, 4]
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "applier",
					Object: `
						set: [5]
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				set: [1, 1, 3, 4, 5]
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("set"),
						_P("set", _V(1)),
						_P("set", _V(3)),
						_P("set", _V(4)),
					),
					"v1",
					false,
				),
				"applier": fieldpath.NewVersionedSet(
					_NS(
						_P("set", _V(5)),
					),
					"v1",
					true,
				),
			},
		},
		"list/ownership/duplicated_items": {
			Ops: []Operation{
				Update{
					Manager: "updater",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			// `name: a` is only owned once.
			Managed: fieldpath.ManagedFields{
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "a")),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
			},
		},
		"list/ownership/change_duplicated_items": {
			Ops: []Operation{
				Update{
					Manager: "updater-one",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "updater-two",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 3
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			// `name: a` is only owned once, by actor who changed some of it.
			Managed: fieldpath.ManagedFields{
				"updater-one": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
				"updater-two": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "a")),
					),
					"v1",
					false,
				),
			},
		},
		"list/ownership/change_fields_duplicated_items": {
			Ops: []Operation{
				Update{
					Manager: "updater-one",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "updater-two",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						  value2: 3 # New field
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			Managed: fieldpath.ManagedFields{
				"updater-one": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
				"updater-two": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "a")),
					),
					"v1",
					false,
				),
			},
		},
		"list/ownership/add_duplicated_items_different_field": {
			Ops: []Operation{
				Update{
					Manager: "updater-one",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "updater-two",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value2: 3 # New field
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			Managed: fieldpath.ManagedFields{
				"updater-one": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
				"updater-two": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "a")),
					),
					"v1",
					false,
				),
			},
		},
		"list/ownership/add_unrelated_to_list_with_duplicates": {
			Ops: []Operation{
				Update{
					Manager: "updater-one",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "updater-two",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			Managed: fieldpath.ManagedFields{
				"updater-one": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "a")),
					),
					"v1",
					false,
				),
				"updater-two": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
			},
		},
		"list/merge/unrelated_with_duplicated_items": {
			Ops: []Operation{
				Update{
					Manager: "updater",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				ForceApply{
					Manager: "applier",
					Object: `
						unrelated: 5
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				list:
				- name: a
				  value1: 1
				- name: a
				  value1: 2
				- name: b
				  value1: 3
				unrelated: 5
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "a")),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
				"applier": fieldpath.NewVersionedSet(
					_NS(
						_P("unrelated"),
					),
					"v1",
					true,
				),
			},
		},
		// TODO: Owning the key is a little messed-up.
		"list/merge/change_duplicated_item": {
			Ops: []Operation{
				Update{
					Manager: "updater",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "applier",
					Object: `
						list:
						- name: a
						  value1: 3
					`,
					APIVersion: "v1",
					Conflicts: merge.Conflicts{
						{Manager: "updater", Path: _P("list", _KBF("name", "a"))},
					},
				},
				ForceApply{
					Manager: "applier",
					Object: `
						list:
						- name: a
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				list:
				- name: a
				  value1: 3
				- name: b
				  value1: 3
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
				"applier": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "a")),
						_P("list", _KBF("name", "a"), "name"),
						_P("list", _KBF("name", "a"), "value1"),
					),
					"v1",
					true,
				),
			},
		},

		"list/merge/unchanged_duplicated_item": {
			Ops: []Operation{
				Update{
					Manager: "updater",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "applier",
					Object: `
						list:
						- name: a
						  value1: 2
					`,
					APIVersion: "v1",
					Conflicts: merge.Conflicts{
						{Manager: "updater", Path: _P("list", _KBF("name", "a"))},
					},
				},
				ForceApply{
					Manager: "applier",
					Object: `
						list:
						- name: a
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				list:
				- name: a
				  value1: 3
				- name: b
				  value1: 3
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					false,
				),
				"applier": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "a")),
						_P("list", _KBF("name", "a"), "name"),
						_P("list", _KBF("name", "a"), "value1"),
					),
					"v1",
					true,
				),
			},
		},
		"list/merge/change_non_duplicated_item": {
			Ops: []Operation{
				Update{
					Manager: "updater",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				ForceApply{
					Manager: "applier",
					Object: `
						list:
						- name: b
						  value1: 4
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				list:
				- name: a
				  value1: 1
				- name: a
				  value1: 2
				- name: b
				  value1: 4
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("list"),
						_P("list", _KBF("name", "a")),
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
					),
					"v1",
					false,
				),
				"applier": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					true,
				),
			},
		},
		"list/merge/apply_update_duplicates_apply_without": {
			Ops: []Operation{
				Apply{
					Manager: "applier",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "updater",
					Object: `
						list:
						- name: a
						  value1: 1
						- name: a
						  value1: 2
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "applier",
					Object: `
						list:
						- name: b
						  value1: 3
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				list:
				- name: a
				  value1: 1
				- name: a
				  value1: 2
				- name: b
				  value1: 3
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"applier": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "b")),
						_P("list", _KBF("name", "b"), "name"),
						_P("list", _KBF("name", "b"), "value1"),
					),
					"v1",
					true,
				),
				"updater": fieldpath.NewVersionedSet(
					_NS(
						_P("list", _KBF("name", "a")),
					),
					"v1",
					false,
				),
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			if err := test.Test(duplicatesParser); err != nil {
				t.Fatal(err)
			}
		})
	}
}
