package printer

import (
	"fmt"
	"strings"

	"github.com/kubescape/k8s-interface/workloadinterface"
	"github.com/kubescape/kubescape/v3/core/cautils"
	"github.com/kubescape/opa-utils/reporthandling/apis"
	"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
	"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
)

type metricsName string

const (
	ksMetrics        metricsName = "kubescape"
	metricsCluster   metricsName = "cluster"
	metricsScore     metricsName = "complianceScore"
	metricsCoverage  metricsName = "coverage_score"
	metricsCount     metricsName = "count"
	metricsFailed    metricsName = "failed"
	metricsSkipped   metricsName = "skipped"
	metricsPassed    metricsName = "passed"
	metricsControl   metricsName = "control"
	metricsControls  metricsName = "controls"
	metricsResource  metricsName = "resource"
	metricsResources metricsName = "resources"
	metricsFramework metricsName = "framework"
)

// ============================================ CLUSTER ============================================================
func (mrs *mComplianceScore) metrics() []string {
	/*
		##### Overall compliance score
		kubescape_cluster_ComplianceScore{} <compliance score>

		###### Overall resources counters
		kubescape_cluster_count_resources_failed{} <counter>
		kubescape_cluster_count_resources_skipped{} <counter>
		kubescape_cluster_count_resources_passed{} <counter>

		###### Overall controls counters
		kubescape_cluster_count_controls_failed{} <counter>
		kubescape_cluster_count_controls_skipped{} <counter>
		kubescape_cluster_count_controls_passed{} <counter>
	*/

	m := []string{}
	// overall
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s", mrs.prefix(), metricsScore), mrs.labels(), mrs.complianceScore))

	// resources
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsResources, metricsFailed), mrs.labels(), mrs.resourcesCountFailed))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsResources, metricsSkipped), mrs.labels(), mrs.resourcesCountSkipped))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsResources, metricsPassed), mrs.labels(), mrs.resourcesCountPassed))

	// controls
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsControl, metricsFailed), mrs.labels(), mrs.controlsCountFailed))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsControl, metricsSkipped), mrs.labels(), mrs.controlsCountSkipped))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsControl, metricsPassed), mrs.labels(), mrs.controlsCountPassed))

	return m
}
func (mrs *mComplianceScore) labels() string {
	return ""
}

func (mrs *mComplianceScore) prefix() string {
	return fmt.Sprintf("%s_%s", ksMetrics, metricsCluster)
}

// ============================================ COVERAGE ============================================================
func (mcs *mScanCoverage) metrics() []string {
	/*
		##### Scan coverage score (how complete the scan was, 100 = full coverage)
		kubescape_cluster_coverage_score{} <coverage score>
	*/
	return []string{
		toRowInMetrics(fmt.Sprintf("%s_%s", mcs.prefix(), metricsCoverage), mcs.labels(), mcs.coverageScore),
	}
}

func (mcs *mScanCoverage) labels() string {
	return ""
}

func (mcs *mScanCoverage) prefix() string {
	return fmt.Sprintf("%s_%s", ksMetrics, metricsCluster)
}

// ============================================ CONTROL ============================================================

func (mcrs *mControlComplianceScore) metrics() []string {
	/*
		# Compliance score
		kubescape_control_complianceScore{name="<control name>",url="<docs url>",severity="<control severity>"} <compliance score>

		# Resources counters
		kubescape_control_count_resources_failed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
		kubescape_control_count_resources_skipped{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
		kubescape_control_count_resources_passed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
	*/

	m := []string{}
	// overall
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s", mcrs.prefix(), metricsScore), mcrs.labels(), mcrs.complianceScore))

	// resources
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mcrs.prefix(), metricsCount, metricsResources, metricsFailed), mcrs.labels(), mcrs.resourcesCountFailed))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mcrs.prefix(), metricsCount, metricsResources, metricsSkipped), mcrs.labels(), mcrs.resourcesCountSkipped))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mcrs.prefix(), metricsCount, metricsResources, metricsPassed), mcrs.labels(), mcrs.resourcesCountPassed))

	return m
}
func (mcrs *mControlComplianceScore) labels() string {
	r := fmt.Sprintf("name=\"%s\"", mcrs.controlName) + ","
	r += fmt.Sprintf("severity=\"%s\"", mcrs.severity) + ","
	r += fmt.Sprintf("link=\"%s\"", mcrs.link)
	return r
}
func (mcrs *mControlComplianceScore) prefix() string {
	return fmt.Sprintf("%s_%s", ksMetrics, metricsControl)
}

// ============================================ FRAMEWORK ============================================================

func (mfrs *mFrameworkComplianceScore) metrics() []string {
	/*
		#### Frameworks metrics
		kubescape_framework_complianceScore{name="<framework name>"} <compliance score>

		###### Frameworks resources counters
		kubescape_framework_count_resources_failed{} <counter>
		kubescape_framework_count_resources_skipped{} <counter>
		kubescape_framework_count_resources_passed{} <counter>

		###### Frameworks controls counters
		kubescape_framework_count_controls_failed{name="<framework name>"} <counter>
		kubescape_framework_count_controls_skipped{name="<framework name>"} <counter>
		kubescape_framework_count_controls_passed{name="<framework name>"} <counter>

	*/

	m := []string{}
	// overall
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s", mfrs.prefix(), metricsScore), mfrs.labels(), mfrs.complianceScore))

	// resources
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsResources, metricsFailed), mfrs.labels(), mfrs.resourcesCountFailed))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsResources, metricsSkipped), mfrs.labels(), mfrs.resourcesCountSkipped))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsResources, metricsPassed), mfrs.labels(), mfrs.resourcesCountPassed))

	// controls
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsControl, metricsFailed), mfrs.labels(), mfrs.controlsCountFailed))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsControl, metricsSkipped), mfrs.labels(), mfrs.controlsCountSkipped))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsControl, metricsPassed), mfrs.labels(), mfrs.controlsCountPassed))

	return m
}
func (mfrs *mFrameworkComplianceScore) labels() string {
	r := fmt.Sprintf("name=\"%s\"", mfrs.frameworkName)
	return r
}
func (mfrs *mFrameworkComplianceScore) prefix() string {
	return fmt.Sprintf("%s_%s", ksMetrics, metricsFramework)
}

// ============================================ RESOURCES ============================================================

func (mrc *mResources) metrics() []string {
	/*
		#### Resources metrics
		kubescape_resource_count_controls_failed{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
		kubescape_resource_count_controls_skipped{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
	*/

	m := []string{}

	// controls
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrc.prefix(), metricsCount, metricsControls, metricsFailed), mrc.labels(), mrc.controlsCountFailed))
	m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrc.prefix(), metricsCount, metricsControls, metricsSkipped), mrc.labels(), mrc.controlsCountSkipped))
	return m
}

func (mrc *mResources) labels() string {
	r := fmt.Sprintf("apiVersion=\"%s\"", mrc.apiVersion) + ","
	r += fmt.Sprintf("kind=\"%s\"", mrc.kind) + ","
	r += fmt.Sprintf("namespace=\"%s\"", mrc.namespace) + ","
	r += fmt.Sprintf("name=\"%s\"", mrc.name)
	return r
}
func (mrc *mResources) prefix() string {
	return fmt.Sprintf("%s_%s", ksMetrics, metricsResource)
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

func toMetricHeader(name, help string) string {
	return fmt.Sprintf("# HELP %s %s\n# TYPE %s gauge", name, help, name)
}

func toRowInMetrics(name string, row string, value int) string {
	return fmt.Sprintf("%s{%s} %d", name, row, value)

}
func emitMetricFamily(lines []string) string {
	if len(lines) == 0 {
		return ""
	}
	emitted := map[string]bool{}
	var r strings.Builder
	for _, line := range lines {
		// extract metric name (everything before '{')
		name := line
		if before, _, ok := strings.Cut(line, "{"); ok {
			name = before
		}
		if !emitted[name] {
			r.WriteString(toMetricHeader(name, name) + "\n")
			emitted[name] = true
		}
		r.WriteString(line + "\n")
	}
	return r.String()
}

func (m *Metrics) String() string {
	// collect all metric lines first, then emit headers once per family
	var all []string
	all = append(all, m.rs.metrics()...)
	all = append(all, m.coverage.metrics()...)
	for i := range m.listFrameworks {
		all = append(all, m.listFrameworks[i].metrics()...)
	}
	for i := range m.listControls {
		all = append(all, m.listControls[i].metrics()...)
	}
	for i := range m.listResources {
		all = append(all, m.listResources[i].metrics()...)
	}
	return emitMetricFamily(all)
}

type mComplianceScore struct {
	resourcesCountPassed  int
	resourcesCountFailed  int
	resourcesCountSkipped int
	controlsCountPassed   int
	controlsCountFailed   int
	controlsCountSkipped  int
	complianceScore       int
}

type mScanCoverage struct {
	coverageScore int
}

type mControlComplianceScore struct {
	controlName           string
	controlID             string
	link                  string
	severity              string
	remediation           string
	resourcesCountPassed  int
	resourcesCountFailed  int
	resourcesCountSkipped int
	complianceScore       int
}

type mFrameworkComplianceScore struct {
	frameworkName         string
	resourcesCountPassed  int
	resourcesCountFailed  int
	resourcesCountSkipped int
	controlsCountPassed   int
	controlsCountFailed   int
	controlsCountSkipped  int
	complianceScore       int
}

type mResources struct {
	name                 string
	namespace            string
	apiVersion           string
	kind                 string
	controlsCountPassed  int
	controlsCountFailed  int
	controlsCountSkipped int
}
type Metrics struct {
	rs             mComplianceScore
	coverage       mScanCoverage
	listFrameworks []mFrameworkComplianceScore
	listControls   []mControlComplianceScore
	listResources  []mResources
}

func (mrs *mComplianceScore) set(resources reportsummary.ICounters, controls reportsummary.ICounters) {
	mrs.resourcesCountSkipped = resources.Skipped()
	mrs.resourcesCountFailed = resources.Failed()
	mrs.resourcesCountPassed = resources.Passed()
	mrs.controlsCountFailed = controls.Failed()
	mrs.controlsCountPassed = controls.Passed()
	mrs.controlsCountSkipped = controls.Skipped()
}

func (mfrs *mFrameworkComplianceScore) set(resources reportsummary.ICounters, controls reportsummary.ICounters) {
	mfrs.resourcesCountSkipped = resources.Skipped()
	mfrs.resourcesCountFailed = resources.Failed()
	mfrs.resourcesCountPassed = resources.Passed()
	mfrs.controlsCountFailed = controls.Failed()
	mfrs.controlsCountPassed = controls.Passed()
	mfrs.controlsCountSkipped = controls.Skipped()
}

func (mcrs *mControlComplianceScore) set(resources reportsummary.ICounters) {
	mcrs.resourcesCountSkipped = resources.Skipped()
	mcrs.resourcesCountFailed = resources.Failed()
	mcrs.resourcesCountPassed = resources.Passed()
}
func (m *Metrics) setComplianceScores(summaryDetails *reportsummary.SummaryDetails) {
	m.rs.set(summaryDetails.NumberOfResources(), summaryDetails.NumberOfControls())
	m.rs.complianceScore = cautils.Float32ToInt(summaryDetails.GetScore())

	for _, fw := range summaryDetails.ListFrameworks() {
		mfrs := mFrameworkComplianceScore{
			frameworkName:   fw.GetName(),
			complianceScore: cautils.Float32ToInt(fw.GetComplianceScore()),
		}
		mfrs.set(fw.NumberOfResources(), fw.NumberOfControls())
		m.listFrameworks = append(m.listFrameworks, mfrs)
	}

	for _, control := range summaryDetails.ListControls() {
		mcrs := mControlComplianceScore{
			controlName:     control.GetName(),
			controlID:       control.GetID(),
			complianceScore: cautils.Float32ToInt(control.GetScore()),
			link:            cautils.GetControlLink(control.GetID()),
			severity:        apis.ControlSeverityToString(control.GetScoreFactor()),
			remediation:     control.GetRemediation(),
		}
		mcrs.set(control.NumberOfResources())
		m.listControls = append(m.listControls, mcrs)
	}
}

func (m *Metrics) setCoverageScore(coverage cautils.ScanCoverage) {
	m.coverage.coverageScore = cautils.Float32ToIntFloor(coverage.CoverageScore)
}

// return -> (passed, skipped, failed)
func resourceControlStatusCounters(result *resourcesresults.Result) (int, int, int) {
	failed := 0
	skipped := 0
	passed := 0
	for i := range result.ListControls() {
		switch result.ListControls()[i].GetStatus(nil).Status() {
		case apis.StatusSkipped:
			skipped++
		case apis.StatusFailed:
			failed++
		case apis.StatusPassed:
			passed++
		}
	}
	return passed, skipped, failed
}

func (m *Metrics) setResourcesCounters(
	resources map[string]workloadinterface.IMetadata,
	results map[string]resourcesresults.Result) {

	for resourceID, result := range results {
		r, ok := resources[resourceID]
		if !ok {
			continue
		}
		passed, skipped, failed := resourceControlStatusCounters(&result)

		mrc := mResources{}
		mrc.apiVersion = r.GetApiVersion()
		mrc.namespace = r.GetNamespace()
		mrc.kind = r.GetKind()
		mrc.name = r.GetName()

		// append
		mrc.controlsCountPassed = passed
		mrc.controlsCountFailed = failed
		mrc.controlsCountSkipped = skipped

		m.listResources = append(m.listResources, mrc)
	}

}
