package daemon

import (
	"time"

	"github.com/ckotzbauer/libstandard"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/grype"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/source"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/source/git"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/source/kubernetes"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/target"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/target/json"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/target/metric"
	"github.com/ckotzbauer/vulnerability-operator/internal/vuln/target/policyreport"
	"github.com/robfig/cron"
	"github.com/sirupsen/logrus"
)

type CronService struct {
	cron    string
	sources []source.Source
	targets []target.Target
}

var running = false

func Start(cronTime string) {
	cr := libstandard.Unescape(cronTime)
	sourceKeys := vuln.OperatorConfig.Sources
	targetKeys := vuln.OperatorConfig.Targets

	logrus.Debugf("Cron set to: %v", cr)
	logrus.Debugf("Sources set to: %v", sourceKeys)
	logrus.Debugf("Targets set to: %v", targetKeys)

	sources := initSources(sourceKeys)
	targets := initTargets(targetKeys)
	cs := CronService{cron: cr, sources: sources, targets: targets}
	cs.printNextExecution()

	c := cron.New()
	err := c.AddFunc(cr, func() { cs.runBackgroundService() })
	if err != nil {
		logrus.WithError(err).Fatal("Could not configure cron")
	}

	c.Start()
}

func (c *CronService) printNextExecution() {
	s, err := cron.Parse(c.cron)
	if err != nil {
		logrus.WithError(err).Fatal("Cron cannot be parsed")
	}

	nextRun := s.Next(time.Now())
	logrus.Debugf("Next background-service run at: %v", nextRun)
}

func (c *CronService) runBackgroundService() {
	if running {
		return
	}

	running = true

	logrus.Info("Execute background-service")
	scanItems := []source.ScanItem{}

	for _, s := range c.sources {
		err := s.Initialize()
		if err != nil {
			logrus.WithError(err).Fatal("Could not initialize source.")
		}

		scanItems = append(scanItems, s.LoadScanItems()...)
	}

	gr, err := grype.New(
		vuln.OperatorConfig.GrypeConfigFile,
		vuln.OperatorConfig.FilterConfigFile,
		vuln.OperatorConfig.MinSeverity,
		vuln.OperatorConfig.OnlyFixed,
		false)

	if err != nil {
		c.printNextExecution()
		running = false
		return
	}

	defer gr.Close()

	scanResult := vuln.ScanResult{FoundVulnerabilities: []vuln.Vulnerability{}, AuditedVulnerabilities: []vuln.Vulnerability{}}

	for _, scanItem := range scanItems {
		result, err := gr.ScanItem(scanItem)

		if err == nil {
			// handle result
			scanResult = vuln.ScanResult{
				FoundVulnerabilities:   append(scanResult.FoundVulnerabilities, result.FoundVulnerabilities...),
				AuditedVulnerabilities: append(scanResult.AuditedVulnerabilities, result.AuditedVulnerabilities...),
			}
		}
	}

	for _, t := range c.targets {
		t.Initialize()
		err := t.ProcessVulns(scanResult)
		if err != nil {
			logrus.WithError(err).Warn("Target could not process vulnerabilities")
		}

		err = t.Finalize()
		if err != nil {
			logrus.WithError(err).Warn("Target could not be finalized")
		}
	}

	c.printNextExecution()
	running = false
}

func initSources(sourceKeys []string) []source.Source {
	sources := make([]source.Source, 0)

	for _, sa := range sourceKeys {
		var err error

		// nolint QF1003
		if sa == "git" {
			workingTree := vuln.OperatorConfig.GitWorkingTree
			workPath := vuln.OperatorConfig.GitPath
			repository := vuln.OperatorConfig.GitRepository
			branch := vuln.OperatorConfig.GitBranch
			token := vuln.OperatorConfig.GitAccessToken
			userName := vuln.OperatorConfig.GitUserName
			password := vuln.OperatorConfig.GitPassword
			githubAppId := vuln.OperatorConfig.GitHubAppId
			githubAppInstallationId := vuln.OperatorConfig.GitHubAppInstallationId
			githubAppPrivateKey := vuln.OperatorConfig.GitHubPrivateKey
			s := git.NewGitSource(workingTree, workPath, repository, branch, token, userName, password, githubAppId, githubAppInstallationId, githubAppPrivateKey)
			err = s.ValidateConfig()
			sources = append(sources, s)
		} else if sa == "kubernetes" {
			namespaceLabelSelector := vuln.OperatorConfig.NamespaceLabelSelector
			podLabelSelector := vuln.OperatorConfig.PodLabelSelector
			s := kubernetes.NewKubernetesSource(namespaceLabelSelector, podLabelSelector)
			err = s.ValidateConfig()
			sources = append(sources, s)
		} else {
			logrus.Fatalf("Unknown source %s", sa)
		}

		if err != nil {
			logrus.WithError(err).Fatal("Config-Validation failed!")
		}
	}

	if len(sources) == 0 {
		logrus.Fatalf("Please specify at least one source.")
	}

	return sources
}

func initTargets(targetKeys []string) []target.Target {
	targets := make([]target.Target, 0)

	for _, ta := range targetKeys {
		var err error
		var t target.Target

		// nolint QF1003
		if ta == "json" {
			reportsDir := vuln.OperatorConfig.ReportsDir
			t = json.NewJsonTarget(reportsDir)
		} else if ta == "metrics" {
			t = metric.NewMetricTarget()
		} else if ta == "policyreport" {
			t = policyreport.NewPolicyReportTarget()
		} else {
			logrus.Fatalf("Unknown target %s", ta)
		}

		err = t.ValidateConfig()
		targets = append(targets, t)

		if err != nil {
			logrus.WithError(err).Fatal("Config-Validation failed!")
		}
	}

	if len(targets) == 0 {
		logrus.Fatalf("Please specify at least one target.")
	}

	return targets
}
