package yaml

import (
	"bufio"
	"bytes"
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"path/filepath"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	yamlutil "k8s.io/apimachinery/pkg/util/yaml"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/shared-provider/tests/framework/env/types"
)

func ReadObjects(paths []string) ([]client.Object, error) {
	objs := []client.Object{}
	for _, path := range paths {
		if err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}
			ext := filepath.Ext(info.Name())
			switch ext {
			case ".yaml", ".yml":
				items, err := parseYAMLFile(path)
				if err != nil {
					return err
				}
				objs = append(objs, items...)
			default:
				return nil
			}

			return nil
		}); err != nil {
			return nil, fmt.Errorf("walk dir %s error: %w", path, err)
		}
	}

	return objs, nil
}

func parseYAMLFile(file string) ([]client.Object, error) {
	objs := []client.Object{}
	docs, err := readFile(file)
	if err != nil {
		return nil, fmt.Errorf("can't read file %s: %w", file, err)
	}
	for i, doc := range docs {
		obj, err := parseObject(doc)
		if err != nil {
			return nil, fmt.Errorf("can't parse %dth doc in file %s: %w", i, file, err)
		}
		objs = append(objs, obj)
	}

	return objs, nil
}

func readFile(path string) ([][]byte, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read file %s error: %w", path, err)
	}

	docs := [][]byte{}
	reader := yamlutil.NewYAMLReader(bufio.NewReader(bytes.NewReader(b)))
	for {
		doc, err := reader.Read()
		if err != nil {
			if errors.Is(err, io.EOF) {
				break
			}

			return nil, fmt.Errorf("read yaml from file %s error: %w", path, err)
		}
		docs = append(docs, doc)
	}

	return docs, nil
}

func parseObject(doc []byte) (client.Object, error) {
	h := sha1.New()
	h.Write(doc)
	hash := hex.EncodeToString(h.Sum(nil))

	obj := &unstructured.Unstructured{}
	if err := yamlutil.Unmarshal(doc, obj); err != nil {
		return nil, fmt.Errorf("yaml unmarshal err: %w", err)
	}
	anno := obj.GetAnnotations()
	if anno == nil {
		anno = map[string]string{}
	}
	anno[types.AnnoKeyManagedBy] = types.AnnoValManagedByProvider
	anno[types.AnnoKeyHash] = hash
	obj.SetAnnotations(anno)

	return obj, nil
}
