/*
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 document

import (
	"fmt"
	"io"
	"path/filepath"

	"sigs.k8s.io/kustomize/api/types"

	"opendev.org/airship/airshipctl/pkg/fs"
)

// KustomNode is used to create name and data to display tree structure
type KustomNode struct {
	Name     string // name used for display purposes (cli)
	Data     string // this could be a Kustomization object, or a string containing a file path
	Children []KustomNode
	Writer   io.Writer
}

// BuildKustomTree creates a tree based on entrypoint
func BuildKustomTree(entrypoint string, writer io.Writer, manifestsDir string) (KustomNode, error) {
	fs := fs.NewDocumentFs()
	name, err := filepath.Rel(manifestsDir, entrypoint)
	if err != nil {
		name = entrypoint
	}
	root := KustomNode{
		Name:     name,
		Data:     entrypoint,
		Children: []KustomNode{},
		Writer:   writer,
	}

	resMap, err := MakeResMap(fs, entrypoint)
	if err != nil {
		return KustomNode{}, err
	}

	for sourceType, sources := range resMap {
		n := KustomNode{
			Name:   sourceType,
			Writer: writer,
		}

		for _, s := range sources {
			if !fs.IsDir(s) {
				name, err := filepath.Rel(manifestsDir, s)
				if err != nil {
					name = s
				}
				n.Children = append(n.Children, KustomNode{
					Name: name,
					Data: s,
				})
			} else {
				s = filepath.Join(s, KustomizationFile)
				child, err := BuildKustomTree(s, writer, "")
				if err != nil {
					return KustomNode{}, err
				}
				n.Children = append(n.Children, child)
			}
		}
		root.Children = append(root.Children, n)
	}
	return root, nil
}

//MakeResMap creates resmap based of kustomize types
func MakeResMap(fs fs.FileSystem, kfile string) (map[string][]string, error) {
	if fs == nil {
		return nil, fmt.Errorf("received nil filesystem")
	}
	bytes, err := fs.ReadFile(kfile)
	if err != nil {
		return nil, err
	}

	k := types.Kustomization{}
	err = k.Unmarshal(bytes)
	if err != nil {
		return nil, err
	}
	basedir := filepath.Dir(kfile)
	var resMap = make(map[string][]string)
	for _, p := range k.Resources {
		path := filepath.Join(basedir, p)
		resMap["Resources"] = append(resMap["Resources"], path)
	}

	for _, p := range k.Crds {
		path := filepath.Join(basedir, p)
		resMap["Crds"] = append(resMap["Crds"], path)
	}

	buildConfigMapAndSecretGenerator(k, basedir, resMap)

	for _, p := range k.Generators {
		path := filepath.Join(basedir, p)
		resMap["Generators"] = append(resMap["Generators"], path)
	}

	for _, p := range k.Transformers {
		path := filepath.Join(basedir, p)
		resMap["Transformers"] = append(resMap["Transformers"], path)
	}

	return resMap, nil
}

func buildConfigMapAndSecretGenerator(k types.Kustomization, basedir string, resMap map[string][]string) {
	for _, p := range k.SecretGenerator {
		for _, s := range p.FileSources {
			path := filepath.Join(basedir, s)
			resMap["SecretGenerator"] = append(resMap["SecretGenerator"], path)
		}
	}
	for _, p := range k.ConfigMapGenerator {
		for _, s := range p.FileSources {
			path := filepath.Join(basedir, s)
			resMap["ConfigMapGenerator"] = append(resMap["ConfigMapGenerator"], path)
		}
	}
}

// PrintTree prints tree view of phase
func (k KustomNode) PrintTree(prefix string) {
	if prefix == "" {
		basedir := filepath.Dir(k.Name)
		dir := filepath.Base(basedir)
		fmt.Fprintf(k.Writer, "%s [%s]\n", dir, basedir)
	}
	for i, child := range k.Children {
		var subprefix string
		knodes := GetKustomChildren(child)
		if len(knodes) > 0 {
			// we found a kustomize file, so print the subtree name first
			if i == len(k.Children)-1 {
				fmt.Fprintf(k.Writer, "%s└── %s\n", prefix, child.Name)
				subprefix = "    "
			} else {
				fmt.Fprintf(k.Writer, "%s├── %s\n", prefix, child.Name)
				subprefix = "│   "
			}
		}
		for j, c := range knodes {
			bd := filepath.Dir(c.Name)
			d := filepath.Base(bd)
			name := fmt.Sprintf("%s [%s]", d, bd)

			if j == len(knodes)-1 {
				fmt.Printf("%s%s└── %s\n", prefix, subprefix, name)
				c.PrintTree(fmt.Sprintf("%s%s    ", prefix, subprefix))
			} else {
				fmt.Printf("%s%s├── %s\n", prefix, subprefix, name)
				c.PrintTree(fmt.Sprintf("%s%s│   ", prefix, subprefix))
			}
		}
	}
}

// GetKustomChildren returns children nodes of kustomnode
func GetKustomChildren(k KustomNode) []KustomNode {
	nodes := []KustomNode{}
	for _, c := range k.Children {
		if len(c.Children) > 0 {
			nodes = append(nodes, c)
		}
	}
	return nodes
}