package diagnostics

import (
	"testing"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)

func TestCollectorCountsOccurrencesAndRetainsDistinctSamplesInOrder(t *testing.T) {
	collector := NewCollector()
	for _, diagnostic := range []contracts.ProducerDiagnostic{
		advisory("one", "first"),
		advisory("one", "first"),
		advisory("two", "second"),
		advisory("three", "third"),
		advisory("four", "fourth"),
	} {
		if err := collector.Add(diagnostic); err != nil {
			t.Fatalf("Add() error = %v", err)
		}
	}

	diagnostics := collector.Diagnostics()
	if len(diagnostics) != 1 {
		t.Fatalf("group count = %d, want 1", len(diagnostics))
	}
	group := diagnostics[0]
	if group.OccurrenceCount != 5 || group.OmittedSampleCount != 2 {
		t.Fatalf("group counts = %#v, want five occurrences and two omitted samples", group)
	}
	if got := []string{group.Samples[0].Scope, group.Samples[1].Scope, group.Samples[2].Scope}; !equalStrings(got, []string{"one", "two", "three"}) {
		t.Fatalf("sample order = %#v, want first three distinct samples", got)
	}
}

func TestCollectorSeparatesGroupsAndRejectsInvalidOrExcessiveGroups(t *testing.T) {
	collector := NewCollector()
	if err := collector.Add(advisory("one", "first")); err != nil {
		t.Fatalf("Add() error = %v", err)
	}
	warning := advisory("two", "second")
	warning.Disposition = contracts.DiagnosticDispositionWarning
	warning.Category = contracts.DiagnosticCategoryFallback
	if err := collector.Add(warning); err != nil {
		t.Fatalf("Add() error = %v", err)
	}
	if got := len(collector.Diagnostics()); got != 2 {
		t.Fatalf("group count = %d, want 2", got)
	}

	invalid := advisory("bad", "bad")
	invalid.ReasonCode = ""
	if err := collector.Add(invalid); err == nil {
		t.Fatal("Add() error = nil, want invalid diagnostic error")
	}

	limited := NewCollector()
	for index := 0; index < contracts.MaxProducerDiagnosticGroups; index++ {
		diagnostic := advisory("scope", "message")
		diagnostic.ReasonCode = "reason-" + string(rune('a'+index))
		if err := limited.Add(diagnostic); err != nil {
			t.Fatalf("Add(%d) error = %v", index, err)
		}
	}
	if err := limited.Add(advisory("overflow", "overflow")); err == nil {
		t.Fatal("Add() error = nil, want local group limit error")
	}
}

func TestCollectorReturnsIndependentSnapshots(t *testing.T) {
	collector := NewCollector()
	if err := collector.Add(advisory("scope", "message")); err != nil {
		t.Fatalf("Add() error = %v", err)
	}
	first := collector.Diagnostics()
	first[0].Samples[0].Message = "changed"
	second := collector.Diagnostics()
	if second[0].Samples[0].Message != "message" {
		t.Fatalf("collector snapshot changed = %#v", second)
	}
}

func advisory(scope string, message string) contracts.ProducerDiagnostic {
	return contracts.ProducerDiagnostic{
		Disposition:     contracts.DiagnosticDispositionAdvisory,
		Category:        contracts.DiagnosticCategoryDataQuality,
		ReasonCode:      "source_unrelated",
		OccurrenceCount: 1,
		Samples:         []contracts.DiagnosticSample{{Scope: scope, Message: message}},
	}
}

func equalStrings(left []string, right []string) bool {
	if len(left) != len(right) {
		return false
	}
	for index := range left {
		if left[index] != right[index] {
			return false
		}
	}
	return true
}
