package cppcheck

import (
	"fmt"
	"log/slog"
	"sort"
	"strings"
)

type Severity int

const (
	ERROR Severity = iota
	WARNING
	PORTABILITY
	PERFORMANCE
	STYLE
	INFORMATION
	DEBUG
	UNKNOWN
)

func severityFromString(severityString string) (Severity, error) {
	switch severityString {
	case "error":
		return ERROR, nil
	case "warning":
		return WARNING, nil
	case "portability":
		return PORTABILITY, nil
	case "performance":
		return PERFORMANCE, nil
	case "style":
		return STYLE, nil
	case "information":
		return INFORMATION, nil
	case "debug":
		return DEBUG, nil
	default:
		return UNKNOWN, fmt.Errorf("unknown severity type \"%s\" for cppcheck", severityString)
	}
}

func GetResult(records []Record, conf Conf) (string, int, error) {
	score := conf.Score
	comment := "### Test results summary\n\n"
	var severityCounts [UNKNOWN + 1]int
	// TODO: remove me
	var severityScore [UNKNOWN + 1]int
	for _, match := range conf.Matches {
		severities := match.Severity
		score := match.Score
		for _, severityString := range severities {
			severity, err := severityFromString(severityString)
			if err != nil {
				return "", 0, err
			}
			severityScore[int(severity)] = score
		}
	}
	totalSeverityScore := 0
	for _, score := range severityScore {
		totalSeverityScore += score
	}
	if totalSeverityScore != 0 {
		for _, record := range records {
			if record.File == "nofile" {
				continue
			}
			severity, err := severityFromString(record.Severity)
			if err != nil {
				slog.Error("parse severity", "error", err)
			}
			severityCounts[int(severity)] += 1
			score -= severityScore[int(severity)]
		}
		comment += fmt.Sprintf("1. error: %d\n", severityCounts[0])
		comment += fmt.Sprintf("2. warning: %d\n", severityCounts[1])
		comment += fmt.Sprintf("3. portability: %d\n", severityCounts[2])
		comment += fmt.Sprintf("4. performance: %d\n", severityCounts[3])
		comment += fmt.Sprintf("5. style: %d\n", severityCounts[4])
		comment += fmt.Sprintf("6. information: %d\n", severityCounts[5])
		comment += fmt.Sprintf("7. debug: %d\n", severityCounts[6])
	}
	matchCount := make(map[string]int)
	scoreChange := make(map[string]int)
	for _, record := range records {
		for _, match := range conf.Matches {
			for _, keyword := range match.Keywords {
				if strings.Contains(record.Id, keyword) ||
					strings.Contains(record.Severity, keyword) {
					matchCount[keyword] += 1
					scoreChange[keyword] += -match.Score
					score += -match.Score
				}
			}
		}
	}
	type Result struct {
		Keyword     string
		Count       int
		ScoreChange int
	}
	var results []Result
	for keyword, count := range matchCount {
		results = append(results, Result{
			Keyword:     keyword,
			Count:       count,
			ScoreChange: scoreChange[keyword],
		})
	}
	sort.Slice(results, func(i, j int) bool {
		if results[i].ScoreChange != results[j].ScoreChange {
			return results[i].ScoreChange < results[j].ScoreChange
		}
		if results[i].Count != results[j].Count {
			return results[i].Count > results[j].Count
		}
		return results[i].Keyword < results[j].Keyword
	})
	for i, result := range results {
		comment += fmt.Sprintf("%d. `%s`: %d occurrence(s), %d point(s)\n",
			i+1, result.Keyword, result.Count, result.ScoreChange)
	}
	return comment, score, nil
}