package installer

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

	"github.com/giolekva/pcloud/core/installer/soft"

	"github.com/go-git/go-billy/v5/memfs"
	"github.com/go-git/go-billy/v5/util"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/storage/memory"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/kube"
)

type ActionConfigFactory struct {
	kubeConfigPath string
}

func NewActionConfigFactory(kubeConfigPath string) ActionConfigFactory {
	return ActionConfigFactory{kubeConfigPath}
}

func (f ActionConfigFactory) New(namespace string) (*action.Configuration, error) {
	config := new(action.Configuration)
	if err := config.Init(
		kube.GetConfig(f.kubeConfigPath, "", namespace),
		namespace,
		"",
		func(fmtString string, args ...any) {
			fmt.Printf(fmtString, args...)
			fmt.Println()
		},
	); err != nil {
		return nil, err
	}
	return config, nil
}

type HelmFetcher interface {
	// TODO(gio): implement integrity check
	Pull(chart HelmChartGitRepo, rfs soft.RepoFS, root string) error
}

type RepoCloner interface {
	Clone(addr, ref string) (*git.Repository, error)
}

type cachingRepoCloner struct {
	cache map[string]*git.Repository
}

func NewCachingRepoCloner() RepoCloner {
	return &cachingRepoCloner{make(map[string]*git.Repository)}
}

func (rc *cachingRepoCloner) Clone(addr, ref string) (*git.Repository, error) {
	key := fmt.Sprintf("%s:%s", addr, ref)
	if ret, ok := rc.cache[key]; ok {
		return ret, nil
	}
	r, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
		URL:           addr,
		ReferenceName: plumbing.ReferenceName(ref),
		SingleBranch:  true,
		Depth:         1,
	})
	if err != nil {
		return nil, err
	}
	// TODO(gio): enable
	// rc.cache[key] = r
	return r, nil
}

type gitHelmFetcher struct {
	rc RepoCloner
}

func NewGitHelmFetcher() *gitHelmFetcher {
	// TODO(gio): take cloner as an argument
	return &gitHelmFetcher{NewCachingRepoCloner()}
}

func (f *gitHelmFetcher) Pull(chart HelmChartGitRepo, rfs soft.RepoFS, root string) error {
	ref := fmt.Sprintf("refs/heads/%s", chart.Branch)
	r, err := f.rc.Clone(chart.Address, ref)
	if err != nil {
		return err
	}
	wt, err := r.Worktree()
	if err != nil {
		return err
	}
	wtFS, err := wt.Filesystem.Chroot(chart.Path)
	if err != nil {
		return err
	}
	if err := rfs.RemoveAll(root); err != nil {
		return err
	}
	return util.Walk(wtFS, "/", func(path string, info fs.FileInfo, err error) error {
		if info.IsDir() {
			return nil
		}
		inp, err := wtFS.Open(path)
		if err != nil {
			return err
		}
		out, err := rfs.Writer(filepath.Join(root, path))
		if err != nil {
			return err
		}
		_, err = io.Copy(out, inp)
		return err
	})
}
