/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

package helm

import (
	"bytes"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"path/filepath"
	"strings"

	"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
	"github.com/edgelesssys/constellation/internal/deploy/helm"
	"github.com/pkg/errors"
	"helm.sh/helm/pkg/ignore"
	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chart/loader"
)

// Run `go generate` to deterministically create the patched Helm deployment for cilium
//go:generate ./generateCilium.sh

//go:embed all:charts/cilium/*
var HelmFS embed.FS

type ChartLoader struct{}

func (i *ChartLoader) Load(csp string) ([]byte, error) {
	ciliumDeployment, err := i.loadCilium(csp)
	if err != nil {
		return nil, err
	}
	deployments := helm.Deployments{Cilium: ciliumDeployment}
	depl, err := json.Marshal(deployments)
	if err != nil {
		return nil, err
	}
	return depl, nil
}

func (i *ChartLoader) loadCilium(csp string) (helm.Deployment, error) {
	chart, err := loadChartsDir(HelmFS, "charts/cilium")
	if err != nil {
		return helm.Deployment{}, err
	}
	var ciliumVals map[string]interface{}
	switch cloudprovider.FromString(csp) {
	case cloudprovider.GCP:
		ciliumVals = gcpVals
	case cloudprovider.Azure:
		ciliumVals = azureVals
	case cloudprovider.QEMU:
		ciliumVals = qemuVals
	default:
		return helm.Deployment{}, fmt.Errorf("unknown csp: %s", csp)
	}
	return helm.Deployment{Chart: chart, Values: ciliumVals}, nil
}

// taken from loader.LoadDir from the helm go module
// loadChartsDir loads from a directory.
//
// This loads charts only from directories.
func loadChartsDir(efs embed.FS, dir string) (*chart.Chart, error) {
	utf8bom := []byte{0xEF, 0xBB, 0xBF}
	// Just used for errors.
	c := &chart.Chart{}

	rules := ignore.Empty()
	ifile, err := efs.ReadFile(filepath.Join(dir, ignore.HelmIgnore))
	if err == nil {
		r, err := ignore.Parse(bytes.NewReader(ifile))
		if err != nil {
			return c, err
		}
		rules = r
	}
	rules.AddDefaults()

	files := []*loader.BufferedFile{}

	walk := func(path string, d fs.DirEntry, err error) error {
		n := strings.TrimPrefix(path, dir)
		if n == "" {
			// No need to process top level. Avoid bug with helmignore .* matching
			// empty names. See issue https://github.com/kubernetes/helm/issues/1776.
			return nil
		}

		// Normalize to / since it will also work on Windows
		n = filepath.ToSlash(n)

		// Check input err
		if err != nil {
			return err
		}

		fi, err := d.Info()
		if err != nil {
			return err
		}

		if d.IsDir() {
			// Directory-based ignore rules should involve skipping the entire
			// contents of that directory.
			if rules.Ignore(n, fi) {
				return filepath.SkipDir
			}
			return nil
		}

		// If a .helmignore file matches, skip this file.
		if rules.Ignore(n, fi) {
			return nil
		}

		// Irregular files include devices, sockets, and other uses of files that
		// are not regular files. In Go they have a file mode type bit set.
		// See https://golang.org/pkg/os/#FileMode for examples.
		if !fi.Mode().IsRegular() {
			return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", path)
		}

		data, err := efs.ReadFile(path)
		if err != nil {
			return errors.Wrapf(err, "error reading %s", n)
		}

		data = bytes.TrimPrefix(data, utf8bom)
		n = strings.TrimPrefix(n, "/")

		files = append(files, &loader.BufferedFile{Name: n, Data: data})
		return nil
	}

	if err := fs.WalkDir(efs, dir, walk); err != nil {
		return c, err
	}

	return loader.LoadFiles(files)
}
