package scanners

import (
	"encoding/json"
	"github.com/joomcode/errorx"
	"github.com/komodorio/helm-dashboard/pkg/dashboard/objects"
	"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
	"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
	"github.com/olekukonko/tablewriter"
	log "github.com/sirupsen/logrus"
	v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
	"strings"
)

type Checkov struct {
	Data *objects.DataLayer
}

func (c *Checkov) ManifestIsScannable() bool {
	return true
}

func (c *Checkov) SupportedResourceKinds() []string {
	// from https://github.com/bridgecrewio/checkov//blob/master/docs/5.Policy%20Index/kubernetes.md
	return []string{
		"AdmissionConfiguration",
		"ClusterRole",
		"ClusterRoleBinding",
		"ConfigMap",
		"CronJob",
		"DaemonSet",
		"Deployment",
		"DeploymentConfig",
		"Ingress",
		"Job",
		"Pod",
		"PodSecurityPolicy",
		"PodTemplate",
		"Policy",
		"ReplicaSet",
		"ReplicationController",
		"Role",
		"RoleBinding",
		"Secret",
		"Service",
		"ServiceAccount",
		"StatefulSet",
	}
}

func (c *Checkov) Name() string {
	return "Checkov"
}

func (c *Checkov) Test() bool {
	utils.FailLogLevel = log.DebugLevel
	defer func() { utils.FailLogLevel = log.WarnLevel }()

	res, err := utils.RunCommand([]string{"checkov", "--version"}, nil)
	if err != nil {
		return false
	}
	log.Infof("Discovered Checkov version: %s", strings.TrimSpace(res))
	return true
}

func (c *Checkov) ScanManifests(mnf string) (*subproc.ScanResults, error) {
	fname, fclose, err := utils.TempFile(mnf)
	if err != nil {
		return nil, err
	}
	defer fclose()

	cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "json", "--file", fname}
	out, err := utils.RunCommand(cmd, nil)
	if err != nil {
		return nil, err
	}

	res := &subproc.ScanResults{}

	err = json.Unmarshal([]byte(out), &res.OrigReport)
	if err != nil {
		return nil, err
	}

	return res, nil
}

func (c *Checkov) ScanResource(ns string, kind string, name string) (*subproc.ScanResults, error) {
	carp := v1.Carp{}
	carp.Kind = kind
	carp.Name = name
	app, err := c.Data.AppForCtx(c.Data.KubeContext)
	if err != nil {
		return nil, errorx.Decorate(err, "failed to get app for context")
	}

	mnf, err := app.K8s.GetResourceYAML(kind, ns, name)
	if err != nil {
		return nil, errorx.Decorate(err, "failed to get YAML for resource")
	}

	fname, fclose, err := utils.TempFile(mnf)
	if err != nil {
		return nil, errorx.Decorate(err, "failed to create temporary file")
	}
	defer fclose()

	cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "json", "--file", fname}
	out, err := utils.RunCommand(cmd, nil)
	if err != nil {
		return nil, err
	}

	cr := CheckovReport{}
	err = json.Unmarshal([]byte(out), &cr)
	if err != nil {
		return nil, err
	}

	res := &subproc.ScanResults{
		PassedCount: cr.Summary.Passed,
		FailedCount: cr.Summary.Failed,
		OrigReport:  checkovReportTable(&cr),
	}

	return res, nil
}

func checkovReportTable(c *CheckovReport) string {
	data := [][]string{}
	for _, item := range c.Results.FailedChecks {
		data = append(data, []string{item.Id, item.Name + "\n", item.Guideline})
	}

	tableString := &strings.Builder{}
	table := tablewriter.NewWriter(tableString)
	table.SetHeader([]string{"ID", "Name", "Guideline"})
	table.SetBorder(false)
	table.SetColWidth(64)
	table.AppendBulk(data)
	table.Render()
	return tableString.String()
}

type CheckovReport struct {
	Summary CheckovSummary `json:"summary"`
	Results CheckovResults `json:"results"`
}

type CheckovSummary struct {
	Failed        int `json:"failed"`
	Passed        int `json:"passed"`
	ResourceCount int `json:"resource_count"`
	// parsing errors?
	// skipped ?
}

type CheckovResults struct {
	FailedChecks []CheckovCheck `json:"failed_checks"`
}

type CheckovCheck struct {
	Id            string `json:"check_id"`
	BcId          string `json:"bc_check_id"`
	Name          string `json:"check_name"`
	Resource      string `json:"resource"`
	Guideline     string `json:"guideline"`
	FileLineRange []int  `json:"file_line_range"`
}
