/*
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/v6/fieldpath"
	. "sigs.k8s.io/structured-merge-diff/v6/internal/fixture"
	"sigs.k8s.io/structured-merge-diff/v6/merge"
	"sigs.k8s.io/structured-merge-diff/v6/typed"
)

// portListParser sets the default value of key "protocol" to "TCP"
var portListParser = func() *typed.Parser {
	parser, err := typed.NewParser(`types:
- name: v1
  map:
    fields:
      - name: containerPorts
        type:
          list:
            elementType:
              map:
                fields:
                - name: port
                  type:
                    scalar: numeric
                - name: protocol
                  default: "TCP"
                  type:
                    scalar: string
                - name: name
                  type:
                    scalar: string
            elementRelationship: associative
            keys:
            - port
            - protocol
`)
	if err != nil {
		panic(err)
	}
	return parser
}()

func TestDefaultKeysFlat(t *testing.T) {
	tests := map[string]TestCase{
		"apply_missing_defaulted_key_A": {
			Ops: []Operation{
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						containerPorts:
						- port: 80
					`,
				},
			},
			APIVersion: "v1",
			Object: `
				containerPorts:
				- port: 80
			`,
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP")),
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "port"),
					),
					"v1",
					false,
				),
			},
		},
		"apply_missing_defaulted_key_B": {
			Ops: []Operation{
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						containerPorts:
						- port: 80
						- port: 80
						  protocol: UDP
					`,
				},
			},
			APIVersion: "v1",
			Object: `
				containerPorts:
				- port: 80
				- port: 80
				  protocol: UDP
			`,
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP")),
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "port"),
						_P("containerPorts", _KBF("port", 80, "protocol", "UDP")),
						_P("containerPorts", _KBF("port", 80, "protocol", "UDP"), "port"),
						_P("containerPorts", _KBF("port", 80, "protocol", "UDP"), "protocol"),
					),
					"v1",
					false,
				),
			},
		},
		"apply_missing_defaulted_key_with_conflict": {
			Ops: []Operation{
				Apply{
					Manager:    "apply-one",
					APIVersion: "v1",
					Object: `
						containerPorts:
						- port: 80
						  protocol: TCP
						  name: foo
					`,
				},
				Apply{
					Manager:    "apply-two",
					APIVersion: "v1",
					Object: `
						containerPorts:
						- port: 80
						  name: bar
					`,
					Conflicts: merge.Conflicts{
						merge.Conflict{Manager: "apply-one", Path: _P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "name")},
					},
				},
			},
			APIVersion: "v1",
			Object: `
				containerPorts:
				- port: 80
				  protocol: TCP
				  name: foo
			`,
			Managed: fieldpath.ManagedFields{
				"apply-one": fieldpath.NewVersionedSet(
					_NS(
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP")),
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "port"),
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "protocol"),
						_P("containerPorts", _KBF("port", 80, "protocol", "TCP"), "name"),
					),
					"v1",
					false,
				),
			},
		},
		"apply_missing_undefaulted_defaulted_key": {
			Ops: []Operation{
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						containerPorts:
						- protocol: TCP
						  name: A
					`,
				},
			},
			APIVersion: "v1",
			Object: `
				containerPorts:
				- protocol: TCP
				  name: A
			`,
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("containerPorts", _KBF("protocol", "TCP")),
						_P("containerPorts", _KBF("protocol", "TCP"), "name"),
						_P("containerPorts", _KBF("protocol", "TCP"), "protocol"),
					),
					"v1",
					true,
				),
			},
		},
	}

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

func TestDefaultKeysFlatErrors(t *testing.T) {
	tests := map[string]TestCase{
		"apply_missing_defaulted_key_ambiguous_A": {
			Ops: []Operation{
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						containerPorts:
						- port: 80
						- port: 80
					`,
				},
			},
		},
		"apply_missing_defaulted_key_ambiguous_B": {
			Ops: []Operation{
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						containerPorts:
						- port: 80
						- port: 80
						  protocol: TCP
					`,
				},
			},
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			if test.Test(portListParser) == nil {
				t.Fatal("Should fail")
			}
		})
	}
}

// bookParser sets the default value of key:
// * "chapter" to 1
// * "section" to "A"
// * "page" to 2,
// * "line" to 3,
var bookParser = func() *typed.Parser {
	parser, err := typed.NewParser(`types:
- name: v1
  map:
    fields:
      - name: book
        type:
          list:
            elementType:
              map:
                fields:
                - name: chapter
                  default: 1
                  type:
                    scalar: numeric
                - name: section
                  default: "A"
                  type:
                    scalar: string
                - name: sentences
                  type:
                    list:
                      elementType:
                        map:
                          fields:
                          - name: page
                            default: 2.0
                            type:
                              scalar: numeric
                          - name: line
                            default: 3
                            type:
                              scalar: numeric
                          - name: text
                            type:
                              scalar: string
                      elementRelationship: associative
                      keys:
                      - page
                      - line
            elementRelationship: associative
            keys:
            - chapter
            - section
`)
	if err != nil {
		panic(err)
	}
	return parser
}()

func TestDefaultKeysNested(t *testing.T) {
	tests := map[string]TestCase{
		"apply_missing_every_key_nested": {
			Ops: []Operation{
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						book:
						- sentences:
						  - text: blah
					`,
				},
			},
			APIVersion: "v1",
			Object: `
				book:
				- sentences:
				  - text: blah
			`,
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P(
							"book", _KBF("chapter", 1, "section", "A"),
						),
						_P(
							"book", _KBF("chapter", 1, "section", "A"),
							"sentences", _KBF("page", 2, "line", 3),
						),
						_P(
							"book", _KBF("chapter", 1, "section", "A"),
							"sentences", _KBF("page", 2, "line", 3),
							"text",
						),
					),
					"v1",
					false,
				),
			},
		},
		"apply_integer_key_with_float_default": {
			Ops: []Operation{
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						book:
						- sentences:
						  - text: blah
					`,
				},
				Apply{
					Manager:    "default",
					APIVersion: "v1",
					Object: `
						book:
						- sentences:
						  - text: blah
						    page: 2
					`,
				},
			},
			APIVersion: "v1",
			Object: `
				book:
				- sentences:
				  - text: blah
				    page: 2
			`,
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P(
							"book", _KBF("chapter", 1, "section", "A"),
						),
						_P(
							"book", _KBF("chapter", 1, "section", "A"),
							"sentences", _KBF("page", 2, "line", 3),
						),
						_P(
							"book", _KBF("chapter", 1, "section", "A"),
							"sentences", _KBF("page", 2, "line", 3),
							"text",
						),
						_P(
							"book", _KBF("chapter", 1, "section", "A"),
							"sentences", _KBF("page", 2, "line", 3),
							"page",
						),
					),
					"v1",
					false,
				),
			},
		},
	}

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