package main

import (
	"bytes"
	"os"
	"path/filepath"
	"reflect"
	"sort"
	"testing"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

func Test(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Main Suite")
}

var _ = Describe("Overlay", func() {
	BeforeEach(func() {
		output = "output"
	})

	It("render simple case properly", func() {
		err := runOverlay(nil, []string{"./cases/simple"})
		Expect(err).To(Succeed())
		fis, err := os.ReadDir("./cases/simple/output")
		Expect(err).To(Succeed())
		Expect(fis).To(HaveLen(1))
	})

	It("render complex case properly", func() {
		err := runOverlay(nil, []string{"./cases/complex"})
		Expect(err).To(Succeed())
		fis, err := os.ReadDir("./cases/complex/output")
		Expect(err).To(Succeed())
		Expect(fis).To(HaveLen(5))
		for _, f := range fis {
			output := filepath.Join("./cases/complex/output/", f.Name())
			expect := filepath.Join("./cases/complex/expect_output/", f.Name())
			b, err := fileEqual(output, expect)
			Expect(err).To(Succeed())
			Expect(b).To(Equal(true))
		}
	})

	It("raise validation error when global version undefined", func() {
		err := runOverlay(nil, []string{"./cases/undefined_global_version"})
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("global default version is not defined"))
	})

	It("raise validation error when tenant's version undefined", func() {
		err := runOverlay(nil, []string{"./cases/undefined_tenant_version"})
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("tenant's version is not defined"))
	})

	It("raise validation error when no enough base configs", func() {
		err := runOverlay(nil, []string{"./cases/no_base_configs"})
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("versioned base config must include all components"))
	})

	It("raise validation error when no enough base images", func() {
		err := runOverlay(nil, []string{"./cases/no_base_images"})
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("global default image"))
	})

	It("use global default version when tenant does not have overrides", func() {
		err := runOverlay(nil, []string{"./cases/tenant_without_version"})
		Expect(err).To(Succeed())
	})

	It("specify immediate policy for tenant without version", func() {
		err := runOverlay(nil, []string{"./cases/undefined_version_with_immediate_policy"})
		Expect(err.Error()).To(ContainSubstring("tenant's update policy is immediate, but version is not defined"))
	})
})

var _ = Describe("Origin", func() {
	oldPrNum := gitPRNum
	oldPrCreatedBy := gitPRCreatedBy
	BeforeEach(func() {
		output = "output_origin"
		gitPRNum = "9527"
		gitPRCreatedBy = "author-9527"
	})
	AfterEach(func() {
		gitPRNum = oldPrNum
		gitPRCreatedBy = oldPrCreatedBy
	})

	It("render complex case without overlay", func() {
		err := keepOrigin(nil, []string{"./cases/complex"})
		Expect(err).To(Succeed())
		fis, err := os.ReadDir("./cases/complex/output_origin")
		Expect(err).To(Succeed())
		Expect(fis).To(HaveLen(8))
		Expect(mustHaveFiles(fis, []string{"base.yaml", "base--awsi32xlarge.yaml", "base--t123456.yaml", "base--t123456--c567890.yaml", "base--v5.0.0--t123456--c56.yaml", "base--v5.0.0.yaml", "base--v5.0.0--awsi32xlarge.yaml", "base--t555.yaml"})).To(Equal(true))
		for _, f := range fis {
			output := filepath.Join("./cases/complex/output_origin/", f.Name())
			expect := filepath.Join("./cases/complex/expect_output_origin/", f.Name())
			expectFileEqual(output, expect)
		}
	})
})

func fileEqual(f1, f2 string) (bool, error) {
	a, err := os.ReadFile(f1)
	if err != nil {
		return false, err
	}

	b, err := os.ReadFile(f2)
	if err != nil {
		return false, err
	}

	return bytes.Equal(a, b), nil
}

func expectFileEqual(actualFilepath, expectedFilepath string) {
	actual, err := os.ReadFile(actualFilepath)
	if err != nil {
		panic(err)
	}

	expected, err := os.ReadFile(expectedFilepath)
	if err != nil {
		panic(err)
	}

	Expect(string(actual)).To(Equal(string(expected)))
}

func mustHaveFiles(fs []os.DirEntry, files []string) bool {
	var outputFiles []string
	for _, f := range fs {
		outputFiles = append(outputFiles, f.Name())
	}
	sort.Strings(outputFiles)
	sort.Strings(files)

	return reflect.DeepEqual(outputFiles, files)
}
