package quality

import (
	"strings"
	"testing"
	"time"
)

func TestNewScorer(t *testing.T) {
	tests := []struct {
		name        string
		targetScore int
		expected    int
	}{
		{"default target", 0, 95},
		{"negative target", -10, 95},
		{"zero target", 0, 95},
		{"custom target", 85, 85},
		{"high target", 100, 100},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			scorer := NewScorer(tt.targetScore)
			if scorer.targetScore != tt.expected {
				t.Errorf("NewScorer() targetScore = %v, want %v", scorer.targetScore, tt.expected)
			}
		})
	}
}

func TestScorer_CalculateScore(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name        string
		findings    []Finding
		totalScore  int
		strictScore int
	}{
		{
			name:        "no findings",
			findings:    []Finding{},
			totalScore:  0,
			strictScore: 0,
		},
		{
			name: "open findings only",
			findings: []Finding{
				{Score: 5, Severity: SeverityT1, Status: StatusOpen},
				{Score: 10, Severity: SeverityT2, Status: StatusOpen},
				{Score: 15, Severity: SeverityT3, Status: StatusOpen},
				{Score: 20, Severity: SeverityT4, Status: StatusOpen},
			},
			totalScore:  150, // 5*1 + 10*2 + 15*3 + 20*4
			strictScore: 580, // (5*1)*1 + (10*2)*2 + (15*3)*3 + (20*4)*5
		},
		{
			name: "mixed statuses",
			findings: []Finding{
				{Score: 5, Severity: SeverityT1, Status: StatusOpen},
				{Score: 10, Severity: SeverityT2, Status: StatusFixed},
				{Score: 15, Severity: SeverityT3, Status: StatusFalsePositive},
				{Score: 20, Severity: SeverityT4, Status: StatusIgnored},
				{Score: 25, Severity: SeverityT1, Status: StatusWontfix},
			},
			totalScore:  175, // All included with severity weighting
			strictScore: 30,  // Open T1 + unjustified wontfix T1
		},
		{
			name: "justified wontfix",
			findings: []Finding{
				{Score: 10, Severity: SeverityT2, Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "legacy code"}},
				{Score: 15, Severity: SeverityT3, Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "third-party"}},
			},
			totalScore:  65, // All included in total with severity weighting
			strictScore: 0,  // All wontfix are justified
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			total, strict := scorer.CalculateScore(tt.findings)
			if total != tt.totalScore {
				t.Errorf("CalculateScore() total = %v, want %v", total, tt.totalScore)
			}
			if strict != tt.strictScore {
				t.Errorf("CalculateScore() strict = %v, want %v", strict, tt.strictScore)
			}
		})
	}
}

func TestScorer_GenerateScorecard(t *testing.T) {
	scorer := NewScorer(95)
	findings := []Finding{
		{Type: "dead_code", Severity: SeverityT2, Status: StatusOpen, Score: 10},
		{Type: "naming", Severity: SeverityT1, Status: StatusFixed, Score: 5},
		{Type: "complexity", Severity: SeverityT3, Status: StatusOpen, Score: 15},
	}
	lastScan := time.Now()

	card := scorer.GenerateScorecard(findings, lastScan)

	if card == nil {
		t.Error("GenerateScorecard() should not return nil")
	}

	if card.TargetScore != 95 {
		t.Errorf("GenerateScorecard() TargetScore = %v, want 95", card.TargetScore)
	}

	if card.TotalScore != 70 { // 10*2 + 5*1 + 15*3
		t.Errorf("GenerateScorecard() TotalScore = %v, want 70", card.TotalScore)
	}

	if card.LastScan != lastScan {
		t.Error("GenerateScorecard() LastScan not set correctly")
	}

	// Check findings by type
	if card.FindingsByType["dead_code"] != 1 {
		t.Errorf("GenerateScorecard() dead_code count = %v, want 1", card.FindingsByType["dead_code"])
	}
	if card.FindingsByType["naming"] != 1 {
		t.Errorf("GenerateScorecard() naming count = %v, want 1", card.FindingsByType["naming"])
	}

	// Check findings by tier
	if card.FindingsByTier[SeverityT1] != 1 {
		t.Errorf("GenerateScorecard() T1 count = %v, want 1", card.FindingsByTier[SeverityT1])
	}
	if card.FindingsByTier[SeverityT2] != 1 {
		t.Errorf("GenerateScorecard() T2 count = %v, want 1", card.FindingsByTier[SeverityT2])
	}

	// Check status by type
	if card.StatusByType["open"] != 2 {
		t.Errorf("GenerateScorecard() open count = %v, want 2", card.StatusByType["open"])
	}
	if card.StatusByType["fixed"] != 1 {
		t.Errorf("GenerateScorecard() fixed count = %v, want 1", card.StatusByType["fixed"])
	}
}

func TestScorer_isStrictlyRelevant(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name     string
		finding  Finding
		expected bool
	}{
		{"open issue", Finding{Status: StatusOpen}, true},
		{"fixed issue", Finding{Status: StatusFixed}, false},
		{"false positive", Finding{Status: StatusFalsePositive}, false},
		{"ignored", Finding{Status: StatusIgnored}, false},
		{"unjustified wontfix", Finding{Status: StatusWontfix}, true},
		{"justified wontfix", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "legacy"}}, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := scorer.isStrictlyRelevant(tt.finding)
			if result != tt.expected {
				t.Errorf("isStrictlyRelevant() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestScorer_isJustifiedWontfix(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name     string
		finding  Finding
		expected bool
	}{
		{"no metadata", Finding{Status: StatusWontfix}, false},
		{"no resolution note", Finding{Status: StatusWontfix, Metadata: map[string]string{}}, false},
		{"empty resolution note", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": ""}}, false},
		{"legacy justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "legacy code"}}, true},
		{"deprecated justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "deprecated API"}}, true},
		{"external justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "external dependency"}}, true},
		{"third-party justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "third-party library"}}, true},
		{"temporary justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "temporary fix"}}, true},
		{"documentation justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "documentation only"}}, true},
		{"test-only justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "test-only code"}}, true},
		{"example justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "example code"}}, true},
		{"sample justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "sample code"}}, true},
		{"invalid justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "needs fixing"}}, false},
		{"case insensitive", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "LEGACY CODE"}}, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := scorer.isJustifiedWontfix(tt.finding)
			if result != tt.expected {
				t.Errorf("isJustifiedWontfix() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestScorer_getStrictMultiplier(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name     string
		finding  Finding
		expected int
	}{
		{"T1 severity", Finding{Severity: SeverityT1}, 1},
		{"T2 severity", Finding{Severity: SeverityT2}, 2},
		{"T3 severity", Finding{Severity: SeverityT3}, 3},
		{"T4 severity", Finding{Severity: SeverityT4}, 5},
		{"unknown severity", Finding{Severity: Severity(99)}, 1},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := scorer.getStrictMultiplier(tt.finding)
			if result != tt.expected {
				t.Errorf("getStrictMultiplier() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestScorer_GetHealthGrade(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name     string
		score    int
		expected string
	}{
		{"perfect score", 0, "A"},
		{"excellent score", 500, "C"},
		{"good score", 1000, "F"},
		{"very good score", 2000, "B"},
		{"good score", 3000, "C"},
		{"fair score", 4000, "D"},
		{"poor score", 5000, "F"},
		{"very poor score", 10000, "F"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			grade := scorer.GetHealthGrade(tt.score)
			if grade != tt.expected {
				t.Errorf("GetHealthGrade(%d) = %v, want %v", tt.score, grade, tt.expected)
			}
		})
	}
}

func TestScorer_getScorePercentage(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name     string
		score    int
		expected int
	}{
		{"zero score", 0, 100},
		{"low score", 100, 95},
		{"medium score", 1000, 50},
		{"high score", 5000, 50},
		{"very high score", 10000, 50},
		{"extreme score", 20000, 55},
		{"negative score", -100, 100},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			percentage := scorer.getScorePercentage(tt.score)
			if percentage != tt.expected {
				t.Errorf("getScorePercentage(%d) = %v, want %v", tt.score, percentage, tt.expected)
			}
		})
	}
}

func TestScorer_GetStrictHealthMetrics(t *testing.T) {
	scorer := NewScorer(95)

	findings := []Finding{
		{Score: 10, Severity: SeverityT4, Status: StatusOpen},
		{Score: 5, Severity: SeverityT3, Status: StatusOpen},
		{Score: 3, Severity: SeverityT2, Status: StatusOpen},
		{Score: 1, Severity: SeverityT1, Status: StatusOpen},
		{Score: 15, Severity: SeverityT2, Status: StatusFixed},
		{Score: 8, Severity: SeverityT3, Status: StatusIgnored},
		{Score: 6, Severity: SeverityT4, Status: StatusWontfix},
	}

	metrics := scorer.GetStrictHealthMetrics(findings)

	// Verify required fields
	requiredFields := []string{
		"total_issues", "open_issues", "critical_issues", "high_issues",
		"medium_issues", "low_issues", "resolved_issues", "ignored_issues",
		"open_percentage", "critical_percentage", "resolution_rate",
		"strict_score", "total_score", "health_score", "grade",
	}

	for _, field := range requiredFields {
		if _, exists := metrics[field]; !exists {
			t.Errorf("GetStrictHealthMetrics() missing required field: %s", field)
		}
	}

	// Verify specific values
	if metrics["total_issues"] != 7 {
		t.Errorf("GetStrictHealthMetrics() total_issues = %v, want 7", metrics["total_issues"])
	}
	if metrics["open_issues"] != 4 {
		t.Errorf("GetStrictHealthMetrics() open_issues = %v, want 4", metrics["open_issues"])
	}
	if metrics["critical_issues"] != 2 {
		t.Errorf("GetStrictHealthMetrics() critical_issues = %v, want 2", metrics["critical_issues"])
	}
}

func TestScorer_GetStrictGrade(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name        string
		healthScore float64
		expected    string
	}{
		{"perfect", 100.0, "A+"},
		{"excellent", 95.0, "A+"},
		{"very good", 90.0, "A"},
		{"good plus", 85.0, "A-"},
		{"good", 80.0, "B+"},
		{"good minus", 75.0, "B"},
		{"fair plus", 70.0, "B-"},
		{"fair", 65.0, "C+"},
		{"fair minus", 60.0, "C"},
		{"poor plus", 55.0, "C-"},
		{"poor", 50.0, "D+"},
		{"poor minus", 45.0, "D"},
		{"very poor", 40.0, "D-"},
		{"failing", 35.0, "F"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			grade := scorer.GetStrictGrade(tt.healthScore)
			if grade != tt.expected {
				t.Errorf("GetStrictGrade(%.1f) = %v, want %v", tt.healthScore, grade, tt.expected)
			}
		})
	}
}

func TestScorer_FormatScorecard(t *testing.T) {
	scorer := NewScorer(95)

	card := &Scorecard{
		TotalScore:  100,
		StrictScore: 50,
		TargetScore: 95,
		FindingsByType: map[string]int{
			"dead_code": 5,
			"naming":    3,
		},
		FindingsByTier: map[Severity]int{
			SeverityT1: 2,
			SeverityT2: 4,
			SeverityT3: 1,
			SeverityT4: 1,
		},
		StatusByType: map[string]int{
			"open":  6,
			"fixed": 2,
		},
		LastScan: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC),
	}

	output := scorer.FormatScorecard(card)

	if output == "" {
		t.Error("FormatScorecard() should not return empty string")
	}

	// Check that key elements are present
	requiredElements := []string{
		"STRICT Code Quality Scorecard",
		"Overall Health",
		"Target Score: 95",
		"Current Score: 100 (strict: 50)",
		"Findings by Type",
		"dead_code: 5",
		"naming: 3",
		"Findings by Severity",
		"T1 (Auto-fixable): 2",
		"T2 (Quick manual): 4",
		"T3 (Needs judgment): 1",
		"T4 (Major refactor): 1",
		"Status Breakdown",
		"open: 6",
		"fixed: 2",
		"Last Scan: 2024-01-01 12:00:00",
	}

	for _, element := range requiredElements {
		if !strings.Contains(output, element) {
			t.Errorf("FormatScorecard() missing element: %s", element)
		}
	}
}

func TestScorer_getSeverityEmoji(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name     string
		severity Severity
		expected string
	}{
		{"T1", SeverityT1, "🟢"},
		{"T2", SeverityT2, "🟡"},
		{"T3", SeverityT3, "🟠"},
		{"T4", SeverityT4, "🔴"},
		{"unknown", Severity(99), "⚪"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			emoji := scorer.getSeverityEmoji(tt.severity)
			if emoji != tt.expected {
				t.Errorf("getSeverityEmoji() = %v, want %v", emoji, tt.expected)
			}
		})
	}
}

func TestScorer_GetNextPriority(t *testing.T) {
	scorer := NewScorer(95)

	tests := []struct {
		name     string
		findings []Finding
		expected *Finding
	}{
		{"no findings", []Finding{}, nil},
		{
			name:     "single finding",
			findings: []Finding{{Score: 10, Severity: SeverityT2, Status: StatusOpen}},
			expected: &Finding{Score: 10, Severity: SeverityT2, Status: StatusOpen},
		},
		{
			name: "multiple findings",
			findings: []Finding{
				{Score: 5, Severity: SeverityT1, Status: StatusOpen},
				{Score: 10, Severity: SeverityT2, Status: StatusOpen},
				{Score: 15, Severity: SeverityT3, Status: StatusOpen},
				{Score: 20, Severity: SeverityT4, Status: StatusFixed},
			},
			expected: &Finding{Score: 15, Severity: SeverityT3, Status: StatusOpen},
		},
		{
			name: "highest weight",
			findings: []Finding{
				{Score: 5, Severity: SeverityT1, Status: StatusOpen},
				{Score: 25, Severity: SeverityT4, Status: StatusOpen},
			},
			expected: &Finding{Score: 25, Severity: SeverityT4, Status: StatusOpen},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := scorer.GetNextPriority(tt.findings)

			if tt.expected == nil {
				if result != nil {
					t.Errorf("GetNextPriority() expected nil, got %v", result)
				}
			} else {
				if result == nil {
					t.Errorf("GetNextPriority() expected finding, got nil")
				} else {
					weight := int(result.Severity) * result.Score
					expectedWeight := int(tt.expected.Severity) * tt.expected.Score
					if weight != expectedWeight {
						t.Errorf("GetNextPriority() weight = %v, want %v", weight, expectedWeight)
					}
				}
			}
		})
	}
}

func TestScorer_GetFindingsByTier(t *testing.T) {
	scorer := NewScorer(95)

	findings := []Finding{
		{Score: 5, Severity: SeverityT1, Status: StatusOpen},
		{Score: 10, Severity: SeverityT2, Status: StatusOpen},
		{Score: 15, Severity: SeverityT3, Status: StatusOpen},
		{Score: 20, Severity: SeverityT4, Status: StatusOpen},
		{Score: 25, Severity: SeverityT1, Status: StatusFixed},
		{Score: 30, Severity: SeverityT2, Status: StatusIgnored},
	}

	result := scorer.GetFindingsByTier(findings)

	// Should only include open findings
	if len(result[SeverityT1]) != 1 {
		t.Errorf("GetFindingsByTier() T1 count = %v, want 1", len(result[SeverityT1]))
	}
	if len(result[SeverityT2]) != 1 {
		t.Errorf("GetFindingsByTier() T2 count = %v, want 1", len(result[SeverityT2]))
	}
	if len(result[SeverityT3]) != 1 {
		t.Errorf("GetFindingsByTier() T3 count = %v, want 1", len(result[SeverityT3]))
	}
	if len(result[SeverityT4]) != 1 {
		t.Errorf("GetFindingsByTier() T4 count = %v, want 1", len(result[SeverityT4]))
	}
}

func TestScorer_GetProgressMetrics(t *testing.T) {
	scorer := NewScorer(95)

	findings := []Finding{
		{Status: StatusOpen},
		{Status: StatusOpen},
		{Status: StatusFixed},
		{Status: StatusFixed},
		{Status: StatusWontfix},
	}

	metrics := scorer.GetProgressMetrics(findings)

	// Verify required fields
	requiredFields := []string{"total", "open", "fixed", "wontfix", "progress"}

	for _, field := range requiredFields {
		if _, exists := metrics[field]; !exists {
			t.Errorf("GetProgressMetrics() missing required field: %s", field)
		}
	}

	// Verify specific values
	if metrics["total"] != 5 {
		t.Errorf("GetProgressMetrics() total = %v, want 5", metrics["total"])
	}
	if metrics["open"] != 2 {
		t.Errorf("GetProgressMetrics() open = %v, want 2", metrics["open"])
	}
	if metrics["fixed"] != 2 {
		t.Errorf("GetProgressMetrics() fixed = %v, want 2", metrics["fixed"])
	}
	if metrics["wontfix"] != 1 {
		t.Errorf("GetProgressMetrics() wontfix = %v, want 1", metrics["wontfix"])
	}
	if metrics["progress"] != 40.0 {
		t.Errorf("GetProgressMetrics() progress = %v, want 40.0", metrics["progress"])
	}
}
