// Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package versioning

import (
	"encoding/xml"
	"strings"
	"testing"
)

func TestParseConfig(t *testing.T) {
	testcases := []struct {
		input            string
		err              error
		excludedPrefixes []string
		excludeFolders   bool
	}{
		{
			input: `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                                  <Status>Enabled</Status>
                                </VersioningConfiguration>`,
			err: nil,
		},
		{
			input: `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                                  <Status>Enabled</Status>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_temporary/</Prefix>
                                  </ExcludedPrefixes>
                                </VersioningConfiguration>`,
			err:              nil,
			excludedPrefixes: []string{"path/to/my/workload/_staging/", "path/to/my/workload/_temporary/"},
		},
		{
			input: `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                                  <Status>Suspended</Status>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging</Prefix>
                                  </ExcludedPrefixes>
                                </VersioningConfiguration>`,
			err: errExcludedPrefixNotSupported,
		},
		{
			input: `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                                  <Status>Enabled</Status>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/ab/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/cd/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/ef/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/gh/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/ij/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/kl/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/mn/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/op/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/qr/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/st/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/uv/</Prefix>
                                  </ExcludedPrefixes>
                                </VersioningConfiguration>`,
			err: errTooManyExcludedPrefixes,
		},
		{
			input: `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                                  <Status>Enabled</Status>
                                  <ExcludeFolders>true</ExcludeFolders>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_staging/</Prefix>
                                  </ExcludedPrefixes>
                                  <ExcludedPrefixes>
                                    <Prefix>path/to/my/workload/_temporary/</Prefix>
                                  </ExcludedPrefixes>
                                </VersioningConfiguration>`,
			err:              nil,
			excludedPrefixes: []string{"path/to/my/workload/_staging/", "path/to/my/workload/_temporary/"},
			excludeFolders:   true,
		},
	}

	for i, tc := range testcases {
		var v *Versioning
		var err error
		v, err = ParseConfig(strings.NewReader(tc.input))
		if tc.err != err {
			t.Fatalf("Test %d: expected %v but got %v", i+1, tc.err, err)
		}
		if err != nil {
			if tc.err == nil {
				t.Fatalf("Test %d: failed due to %v", i+1, err)
			}
		} else {
			if err := v.Validate(); tc.err != err {
				t.Fatalf("Test %d: validation failed due to %v", i+1, err)
			}
			if len(tc.excludedPrefixes) > 0 {
				var mismatch bool
				if len(v.ExcludedPrefixes) != len(tc.excludedPrefixes) {
					t.Fatalf("Test %d: Expected length of excluded prefixes %d but got %d", i+1, len(tc.excludedPrefixes), len(v.ExcludedPrefixes))
				}
				var i int
				var eprefix string
				for i, eprefix = range tc.excludedPrefixes {
					if eprefix != v.ExcludedPrefixes[i].Prefix {
						mismatch = true
						break
					}
				}
				if mismatch {
					t.Fatalf("Test %d: Expected excluded prefix %s but got %s", i+1, tc.excludedPrefixes[i], v.ExcludedPrefixes[i].Prefix)
				}
			}
			if tc.excludeFolders != v.ExcludeFolders {
				t.Fatalf("Test %d: Expected ExcludeFoldersr=%v but got %v", i+1, tc.excludeFolders, v.ExcludeFolders)
			}
		}
	}
}

func TestMarshalXML(t *testing.T) {
	// Validates if Versioning with no excluded prefixes omits
	// ExcludedPrefixes tags
	v := Versioning{
		Status: Enabled,
	}
	buf, err := xml.Marshal(v)
	if err != nil {
		t.Fatalf("Failed to marshal %v: %v", v, err)
	}

	str := string(buf)
	if strings.Contains(str, "ExcludedPrefixes") {
		t.Fatalf("XML shouldn't contain ExcludedPrefixes tag - %s", str)
	}
}

func TestVersioningZero(t *testing.T) {
	var v Versioning
	if v.Enabled() {
		t.Fatalf("Expected to be disabled but got enabled")
	}
	if v.Suspended() {
		t.Fatalf("Expected to be disabled but got suspended")
	}
}

func TestExcludeFolders(t *testing.T) {
	v := Versioning{
		Status:         Enabled,
		ExcludeFolders: true,
	}
	testPrefixes := []string{"jobs/output/_temporary/", "jobs/output/", "jobs/"}
	for i, prefix := range testPrefixes {
		if v.PrefixEnabled(prefix) || !v.PrefixSuspended(prefix) {
			t.Fatalf("Test %d: Expected versioning to be excluded for %s", i+1, prefix)
		}
	}

	// Test applicability for regular objects
	if prefix := "prefix-1/obj-1"; !v.PrefixEnabled(prefix) || v.PrefixSuspended(prefix) {
		t.Fatalf("Expected versioning to be enabled for %s", prefix)
	}

	// Test when ExcludeFolders is disabled
	v.ExcludeFolders = false
	for i, prefix := range testPrefixes {
		if !v.PrefixEnabled(prefix) || v.PrefixSuspended(prefix) {
			t.Fatalf("Test %d: Expected versioning to be enabled for %s", i+1, prefix)
		}
	}
}

func TestExcludedPrefixesMatch(t *testing.T) {
	v := Versioning{
		Status:           Enabled,
		ExcludedPrefixes: []ExcludedPrefix{{"*/_temporary/"}},
	}

	if err := v.Validate(); err != nil {
		t.Fatalf("Invalid test versioning config %v: %v", v, err)
	}
	tests := []struct {
		prefix   string
		excluded bool
	}{
		{
			prefix:   "app1-jobs/output/_temporary/attempt1/data.csv",
			excluded: true,
		},
		{
			prefix:   "app1-jobs/output/final/attempt1/data.csv",
			excluded: false,
		},
	}

	for i, test := range tests {
		if v.PrefixSuspended(test.prefix) != test.excluded {
			if test.excluded {
				t.Fatalf("Test %d: Expected prefix %s to be excluded from versioning", i+1, test.prefix)
			} else {
				t.Fatalf("Test %d: Expected prefix %s to have versioning enabled", i+1, test.prefix)
			}
		}
	}
}