package report

import (
	"context"
	"io"

	"golang.org/x/xerrors"

	defsecTypes "github.com/aquasecurity/defsec/pkg/types"
	dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
	"github.com/aquasecurity/trivy/pkg/compliance/spec"
	"github.com/aquasecurity/trivy/pkg/types"
)

const (
	allReport     = "all"
	summaryReport = "summary"
)

type Option struct {
	Format        types.Format
	Report        string
	Output        io.Writer
	Severities    []dbTypes.Severity
	ColumnHeading []string
}

// ComplianceReport represents a kubernetes scan report
type ComplianceReport struct {
	ID               string
	Title            string
	Description      string
	Version          string
	RelatedResources []string
	Results          []*ControlCheckResult
}

type ControlCheckResult struct {
	ID            string
	Name          string
	Description   string
	DefaultStatus defsecTypes.ControlStatus `json:",omitempty"`
	Severity      string
	Results       types.Results
}

// SummaryReport represents a kubernetes scan report with consolidated findings
type SummaryReport struct {
	SchemaVersion   int `json:",omitempty"`
	ID              string
	Title           string
	SummaryControls []ControlCheckSummary `json:",omitempty"`
}

type ControlCheckSummary struct {
	ID        string
	Name      string
	Severity  string
	TotalFail *int `json:",omitempty"`
}

// Writer defines the result write operation
type Writer interface {
	Write(ComplianceReport) error
}

// Write writes the results in the given format
func Write(ctx context.Context, report *ComplianceReport, option Option) error {
	switch option.Format {
	case types.FormatJSON:
		jwriter := JSONWriter{
			Output: option.Output,
			Report: option.Report,
		}
		return jwriter.Write(report)
	case types.FormatTable:
		if !report.empty() {
			complianceWriter := &TableWriter{
				Output:     option.Output,
				Report:     option.Report,
				Severities: option.Severities,
			}
			err := complianceWriter.Write(ctx, report)
			if err != nil {
				return err
			}
		}
		return nil
	default:
		return xerrors.Errorf(`unknown format %q. Use "json" or "table"`, option.Format)
	}
}

func (r ComplianceReport) empty() bool {
	return len(r.Results) == 0
}

// buildControlCheckResults create compliance results data
func buildControlCheckResults(checksMap map[string]types.Results, controls []defsecTypes.Control) []*ControlCheckResult {
	var complianceResults []*ControlCheckResult
	for _, control := range controls {
		var results types.Results
		for _, c := range control.Checks {
			results = append(results, checksMap[c.ID]...)
		}
		complianceResults = append(complianceResults, &ControlCheckResult{
			Name:          control.Name,
			ID:            control.ID,
			Description:   control.Description,
			Severity:      string(control.Severity),
			DefaultStatus: control.DefaultStatus,
			Results:       results,
		})
	}
	return complianceResults
}

// buildComplianceReportResults create compliance results data
func buildComplianceReportResults(checksMap map[string]types.Results, s defsecTypes.Spec) *ComplianceReport {
	controlCheckResult := buildControlCheckResults(checksMap, s.Controls)
	return &ComplianceReport{
		ID:               s.ID,
		Title:            s.Title,
		Description:      s.Description,
		Version:          s.Version,
		RelatedResources: s.RelatedResources,
		Results:          controlCheckResult,
	}
}

func BuildComplianceReport(scanResults []types.Results, cs spec.ComplianceSpec) (*ComplianceReport, error) {
	// aggregate checks by ID
	aggregateChecksByID := spec.AggregateAllChecksBySpecID(scanResults, cs)

	// build compliance report results
	return buildComplianceReportResults(aggregateChecksByID, cs.Spec), nil
}
