/*
 * Copyright (c) Microsoft Corporation.
 * Licensed under the MIT license.
 */

package config

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	. "github.com/onsi/gomega"

	"gopkg.in/yaml.v3"

	"github.com/Azure/azure-service-operator/v2/tools/generator/internal/astmodel"
	"github.com/Azure/azure-service-operator/v2/tools/generator/internal/test"
)

func TestGroupConfiguration_WhenYAMLWellFormed_ReturnsExpectedResult(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)

	yamlBytes := loadTestData(t)

	var group GroupConfiguration
	err := yaml.Unmarshal(yamlBytes, &group)
	g.Expect(err).To(Succeed())
	// Check for exact versions present in the YAML
	g.Expect(group.versions).To(HaveKey("2021-01-01"))
	g.Expect(group.versions).To(HaveKey("2021-05-15"))
	// Check for local package name equivalents
	g.Expect(group.versions).To(HaveKey("v1api20210101"))
	g.Expect(group.versions).To(HaveKey("v1api20210515"))
}

func TestGroupConfiguration_WhenYAMLBadlyFormed_ReturnsError(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)
	yamlBytes := loadTestData(t)

	var group GroupConfiguration
	err := yaml.Unmarshal(yamlBytes, &group)
	g.Expect(err).NotTo(Succeed())
}

func TestGroupConfiguration_FindVersion_GivenTypeName_ReturnsExpectedVersion(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)

	ver := "2021-01-01"
	refTest := test.MakeLocalPackageReference("demo", ver)
	refOther := test.MakeLocalPackageReference("demo", "2022-12-31")
	refAlpha := astmodel.MakeLocalPackageReference("prefix", "demo", "v1alpha1api", ver)
	refBeta := astmodel.MakeLocalPackageReference("prefix", "demo", "v1beta", ver)

	groupConfiguration := NewGroupConfiguration("demo")
	versionConfig := NewVersionConfiguration("2021-01-01")
	g.Expect(groupConfiguration.addVersion(versionConfig.name, versionConfig)).To(Succeed())

	cases := []struct {
		name          string
		ref           astmodel.PackageReference
		expectedFound bool
	}{
		{"Lookup by version", refTest, true},
		{"Lookup by alpha version", refAlpha, true},
		{"Lookup by beta version", refBeta, true},
		{"Lookup by other version", refOther, false},
	}

	for _, c := range cases {
		c := c
		t.Run(c.name, func(t *testing.T) {
			t.Parallel()
			g := NewGomegaWithT(t)

			v := groupConfiguration.findVersion(c.ref)
			if c.expectedFound {
				g.Expect(v).To(Equal(versionConfig))
			} else {
				g.Expect(v).To(BeNil())
			}
		})
	}
}

func loadTestData(t *testing.T) []byte {
	testName := t.Name()
	index := strings.Index(testName, "_")

	folder := testName[0:index]
	file := string(testName[index+1:]) + ".yaml"
	yamlPath := filepath.Join("testdata", folder, file)

	yamlBytes, err := os.ReadFile(yamlPath)
	if err != nil {
		// If the file doesn't exist we fail the test
		t.Fatalf("unable to load %s (%s)", yamlPath, err)
	}

	return yamlBytes
}

func TestGroupConfiguration_WhenVersionConfigurationNotConsumed_ReturnsErrorWithExpectedTip(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)

	// Create configuration with the wrong version
	typeConfig := NewTypeConfiguration("Person")
	typeConfig.SupportedFrom.Set("vNext")

	versionConfig := NewVersionConfiguration("2022-01-01")
	g.Expect(versionConfig.addType(typeConfig.name, typeConfig)).To(Succeed())

	groupConfig := NewGroupConfiguration("demo")
	g.Expect(groupConfig.addVersion(versionConfig.name, versionConfig)).To(Succeed())

	omConfig := NewObjectModelConfiguration()
	g.Expect(omConfig.addGroup(groupConfig.name, groupConfig)).To(Succeed())

	// Lookup $supportedFrom for our type - version is from 2021 but our config has 2022, so it won't be found
	tn := astmodel.MakeInternalTypeName(
		test.MakeLocalPackageReference(groupConfig.name, "2021-01-01"),
		"Person")

	_, ok := omConfig.SupportedFrom.Lookup(tn)
	g.Expect(ok).To(BeFalse())

	err := omConfig.SupportedFrom.VerifyConsumed()
	g.Expect(err).NotTo(BeNil())                                   // We expect an error, config hasn't been used
	g.Expect(err.Error()).To(ContainSubstring("did you mean"))     // We want to receive a tip
	g.Expect(err.Error()).To(ContainSubstring(versionConfig.name)) // and we want the correct version to be suggested
}

/*
 * PayloadType tests
 */

func TestGroupConfiguration_LookupPayloadType_WhenConfigured_ReturnsExpectedResult(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)
	groupConfig := NewGroupConfiguration("Network")
	groupConfig.PayloadType.Set(ExplicitProperties)

	payloadType, ok := groupConfig.PayloadType.Lookup()
	g.Expect(payloadType).To(Equal(ExplicitProperties))
	g.Expect(ok).To(BeTrue())
}

func TestGroupConfiguration_LookupPayloadType_WhenNotConfigured_ReturnsExpectedResult(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)
	groupConfig := NewGroupConfiguration("Network")

	name, ok := groupConfig.PayloadType.Lookup()
	g.Expect(name).To(Equal(PayloadType("")))
	g.Expect(ok).To(BeFalse())
}

func TestGroupConfiguration_VerifyPayloadTypeConsumed_WhenConsumed_ReturnsNoError(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)

	groupConfig := NewGroupConfiguration("Network")
	groupConfig.PayloadType.Set(OmitEmptyProperties)

	_, ok := groupConfig.PayloadType.Lookup()
	g.Expect(ok).To(BeTrue())
	g.Expect(groupConfig.PayloadType.VerifyConsumed()).To(Succeed())
}

func TestGroupConfiguration_VerifyPayloadTypeConsumed_WhenNotConsumed_ReturnsExpectedError(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)

	groupConfig := NewGroupConfiguration("Network")
	groupConfig.PayloadType.Set(ExplicitProperties)

	err := groupConfig.PayloadType.VerifyConsumed()
	g.Expect(err).NotTo(BeNil())
	g.Expect(err.Error()).To(ContainSubstring(groupConfig.name))
}

/*
 * Duplicate Key Detection Tests
 */

func TestGroupConfiguration_UnmarshalYAML_WhenDuplicateVersions_ReturnsError(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)

	yamlContent := `
2021-01-01:
  SomeType: {}
2021-01-01:
  OtherType: {}
`

	var gc GroupConfiguration
	err := yaml.Unmarshal([]byte(yamlContent), &gc)
	g.Expect(err).NotTo(Succeed())
	g.Expect(err.Error()).To(ContainSubstring("duplicate version configuration"))
	g.Expect(err.Error()).To(ContainSubstring("2021-01-01"))
}

func TestGroupConfiguration_UnmarshalYAML_WhenDuplicateVersionsCaseInsensitive_ReturnsError(t *testing.T) {
	t.Parallel()
	g := NewGomegaWithT(t)

	yamlContent := `
2021-01-01:
  SomeType: {}
2021-01-01:
  OtherType: {}
`

	var gc GroupConfiguration
	err := yaml.Unmarshal([]byte(yamlContent), &gc)
	g.Expect(err).NotTo(Succeed())
	g.Expect(err.Error()).To(ContainSubstring("duplicate version configuration"))
	g.Expect(err.Error()).To(ContainSubstring("2021-01-01"))
}
