package git

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/ckotzbauer/libk8soci/pkg/git"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/source"
	"github.com/sirupsen/logrus"
)

type GitSource struct {
	workingTree string
	workPath    string
	repository  string
	branch      string
	gitAccount  git.GitAccount
	sboms       []source.ScanItem
}

func NewGitSource(workingTree, workPath, repository, branch, token, userName, password, githubAppID, githubInstallationID, githubPrivateKey string) *GitSource {
	gitAccount := git.New("", "", token, userName, password, githubAppID, githubInstallationID, githubPrivateKey)
	return &GitSource{
		workingTree: workingTree,
		workPath:    workPath,
		repository:  repository,
		branch:      branch,
		gitAccount:  gitAccount,
		sboms:       []source.ScanItem{},
	}
}

func (g *GitSource) ValidateConfig() error {
	if g.workingTree == "" {
		return fmt.Errorf("%s is empty", vuln.ConfigKeyGitWorkingTree)
	}

	if g.repository == "" {
		return fmt.Errorf("%s is empty", vuln.ConfigKeyGitRepository)
	}

	if g.branch == "" {
		return fmt.Errorf("%s is empty", vuln.ConfigKeyGitBranch)
	}

	return nil
}

func (g *GitSource) Initialize() error {
	return g.gitAccount.PrepareRepository(
		g.repository, g.workingTree,
		g.branch)
}

func (g *GitSource) LoadScanItems() []source.ScanItem {
	g.sboms = []source.ScanItem{}
	ignoreDirs := []string{".git"}
	sbomFileNames := []string{"sbom.json", "sbom.txt", "sbom.xml", "sbom.spdx"}
	err := filepath.Walk(filepath.Join(g.workingTree, g.workPath), g.filterSboms(ignoreDirs, sbomFileNames))
	if err != nil {
		logrus.WithError(err).Error("Could load SBOMs")
	}

	return g.sboms
}

func (g *GitSource) filterSboms(ignoreDirs, sbomFileNames []string) filepath.WalkFunc {
	return func(p string, info os.FileInfo, err error) error {
		if err != nil {
			logrus.WithError(err).Errorf("An error occurred while processing %s", p)
			return nil
		}

		if info.IsDir() {
			dir := filepath.Base(p)
			for _, d := range ignoreDirs {
				if d == dir {
					return filepath.SkipDir
				}
			}
		}

		file := filepath.Base(p)
		for _, f := range sbomFileNames {
			if f == file {
				/* #nosec */
				content, err := os.ReadFile(p)
				if err != nil {
					logrus.WithError(err).Errorf("An error occurred while reading file %s", p)
					continue
				}

				imageID := g.extractImageIDFromFile(p)
				sbom := source.Sbom{Sbom: string(content), ImageID: imageID}
				g.sboms = append(g.sboms, sbom)
			}
		}

		return nil
	}
}

func (g *GitSource) extractImageIDFromFile(f string) string {
	cleaned, _ := filepath.Rel(g.workingTree, f)
	cleaned, _ = filepath.Rel(g.workPath, cleaned)
	cleaned = strings.Replace(cleaned, "/sha256_", "@sha256:", 1)
	cleaned = filepath.Dir(cleaned)
	return cleaned
}
