package output

import (
	"encoding/json"
	"io"

	"github.com/garagon/aguara/internal/baseline"
	"github.com/garagon/aguara/internal/scanner"
)

// ToolVersion is the aguara version reported in SARIF output.
var ToolVersion = "dev"

// SARIFFormatter outputs findings in SARIF 2.1.0 format for GitHub Code Scanning.
type SARIFFormatter struct{}

type sarifLog struct {
	Schema  string     `json:"$schema"`
	Version string     `json:"version"`
	Runs    []sarifRun `json:"runs"`
}

type sarifRun struct {
	Tool       sarifTool      `json:"tool"`
	Results    []sarifResult  `json:"results"`
	Properties map[string]any `json:"properties,omitempty"`
}

type sarifTool struct {
	Driver sarifDriver `json:"driver"`
}

type sarifDriver struct {
	Name           string      `json:"name"`
	Version        string      `json:"version"`
	InformationURI string      `json:"informationUri"`
	Rules          []sarifRule `json:"rules"`
}

type sarifRule struct {
	ID               string              `json:"id"`
	Name             string              `json:"name"`
	ShortDescription sarifMessage        `json:"shortDescription"`
	Help             *sarifMessage       `json:"help,omitempty"`
	DefaultConfig    sarifDefaultConfig  `json:"defaultConfiguration"`
	Properties       sarifRuleProperties `json:"properties"`
}

type sarifDefaultConfig struct {
	Level string `json:"level"`
}

type sarifRuleProperties struct {
	Tags []string `json:"tags,omitempty"`
}

type sarifMessage struct {
	Text string `json:"text"`
}

type sarifResult struct {
	RuleID              string            `json:"ruleId"`
	RuleIndex           int               `json:"ruleIndex"`
	Level               string            `json:"level"`
	Message             sarifMessage      `json:"message"`
	Locations           []sarifLocation   `json:"locations"`
	PartialFingerprints map[string]string `json:"partialFingerprints,omitempty"`
	Properties          map[string]any    `json:"properties,omitempty"`
}

type sarifLocation struct {
	PhysicalLocation sarifPhysicalLocation `json:"physicalLocation"`
}

type sarifPhysicalLocation struct {
	ArtifactLocation sarifArtifactLocation `json:"artifactLocation"`
	Region           sarifRegion           `json:"region"`
}

type sarifArtifactLocation struct {
	URI string `json:"uri"`
}

type sarifRegion struct {
	StartLine   int `json:"startLine"`
	StartColumn int `json:"startColumn,omitempty"`
}

func (f *SARIFFormatter) Format(w io.Writer, result *scanner.ScanResult) error {
	// Collect unique rules in order
	ruleIndex := map[string]int{}
	var rules []sarifRule
	for _, finding := range result.Findings {
		if _, ok := ruleIndex[finding.RuleID]; !ok {
			ruleIndex[finding.RuleID] = len(rules)
			sr := sarifRule{
				ID:               finding.RuleID,
				Name:             finding.RuleName,
				ShortDescription: sarifMessage{Text: finding.RuleName},
				DefaultConfig:    sarifDefaultConfig{Level: severityToLevel(finding.Severity)},
				Properties:       sarifRuleProperties{Tags: []string{finding.Category}},
			}
			if finding.Remediation != "" {
				sr.Help = &sarifMessage{Text: finding.Remediation}
			}
			rules = append(rules, sr)
		}
	}

	// Build results
	var results []sarifResult
	for _, finding := range result.Findings {
		line := max(finding.Line, 1)
		col := max(finding.Column, 1)
		r := sarifResult{
			RuleID:    finding.RuleID,
			RuleIndex: ruleIndex[finding.RuleID],
			Level:     severityToLevel(finding.Severity),
			Message:   sarifMessage{Text: finding.RuleName + ": " + finding.MatchedText},
			Locations: []sarifLocation{
				{
					PhysicalLocation: sarifPhysicalLocation{
						ArtifactLocation: sarifArtifactLocation{URI: finding.FilePath},
						Region:           sarifRegion{StartLine: line, StartColumn: col},
					},
				},
			},
		}
		// Emit a stable fingerprint for baselineable findings so
		// GitHub code-scanning (and external baseline tooling) can
		// de-dupe across runs. Sensitive / credential-leak findings are
		// non-baselineable and intentionally carry no fingerprint.
		if baseline.Baselineable(finding) {
			r.PartialFingerprints = map[string]string{
				baseline.FingerprintKey: string(baseline.ComputeFingerprint(finding)),
			}
		}
		props := map[string]any{}
		if finding.InCodeBlock {
			props["in_code_block"] = true
		}
		if finding.Confidence > 0 {
			props["confidence"] = finding.Confidence
			// SARIF rank convention: 0-100 scale
			props["rank"] = finding.Confidence * 100
		}
		if len(props) > 0 {
			r.Properties = props
		}
		results = append(results, r)
	}

	log := sarifLog{
		Schema:  "https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-schema-2.1.0.json",
		Version: "2.1.0",
		Runs: []sarifRun{
			{
				Tool: sarifTool{
					Driver: sarifDriver{
						Name:           "aguara",
						Version:        ToolVersion,
						InformationURI: "https://github.com/garagon/aguara",
						Rules:          rules,
					},
				},
				Results: results,
				Properties: map[string]any{
					"duration_ms": result.Duration.Milliseconds(),
					"riskScore":   result.RiskScore,
				},
			},
		},
	}

	enc := json.NewEncoder(w)
	enc.SetIndent("", "  ")
	return enc.Encode(log)
}

func severityToLevel(sev scanner.Severity) string {
	switch sev {
	case scanner.SeverityCritical:
		return "error"
	case scanner.SeverityHigh:
		return "warning"
	case scanner.SeverityMedium, scanner.SeverityLow:
		return "note"
	case scanner.SeverityInfo:
		return "none"
	default:
		return "none"
	}
}
