package rules

import (
	"testing"
	"time"

	"github.com/nexuscrm/sentry/internal/types"
)

type fakeWindow struct{ inWindow bool }

func (f fakeWindow) InSuppressionWindow(_ time.Time) bool { return f.inWindow }

func resultWarn(rule, entity string) types.RuleResult {
	return types.RuleResult{RuleID: rule, Entity: entity, Level: "WARN", Breached: true}
}

func resultCrit(rule, entity string) types.RuleResult {
	return types.RuleResult{RuleID: rule, Entity: entity, Level: "CRIT", Breached: true}
}

func TestSuppressor_ColdStart(t *testing.T) {
	s := NewSuppressor(fakeWindow{}, 2, time.Hour)
	ts := time.Now()
	// Cycles 1 and 2 must be quiet
	for i := 0; i < 2; i++ {
		r := s.Filter(ts.Add(time.Duration(i)*time.Minute), []types.RuleResult{resultWarn("data.row_spike_warn", "Contact")})
		if len(r) != 0 {
			t.Errorf("cycle %d should be quiet, got %d results", i+1, len(r))
		}
	}
	// Cycle 3 is live
	r := s.Filter(ts.Add(2*time.Minute), []types.RuleResult{resultWarn("data.row_spike_warn", "Contact")})
	if len(r) != 1 {
		t.Errorf("cycle 3 should fire, got %d", len(r))
	}
}

func TestSuppressor_DedupWindow(t *testing.T) {
	s := NewSuppressor(fakeWindow{}, 0, 60*time.Minute) // no cold-start
	ts := time.Date(2026, 4, 12, 9, 0, 0, 0, time.UTC)
	first := s.Filter(ts, []types.RuleResult{resultWarn("data.row_spike_warn", "Contact")})
	if len(first) != 1 {
		t.Fatalf("first fire len=%d", len(first))
	}

	// 30 min later — same rule, still in cooldown
	second := s.Filter(ts.Add(30*time.Minute), []types.RuleResult{resultWarn("data.row_spike_warn", "Contact")})
	if len(second) != 0 {
		t.Errorf("within cooldown should be muted")
	}

	// 61 min later — cooldown expired
	third := s.Filter(ts.Add(61*time.Minute), []types.RuleResult{resultWarn("data.row_spike_warn", "Contact")})
	if len(third) != 1 {
		t.Errorf("after cooldown should fire again")
	}
}

func TestSuppressor_EscalationBypassesCooldown(t *testing.T) {
	s := NewSuppressor(fakeWindow{}, 0, 60*time.Minute)
	ts := time.Date(2026, 4, 12, 9, 0, 0, 0, time.UTC)
	_ = s.Filter(ts, []types.RuleResult{resultWarn("data.row_spike_warn", "Contact")})
	// 10 min later — CRIT on same entity (escalation)
	r := s.Filter(ts.Add(10*time.Minute), []types.RuleResult{resultCrit("data.row_spike_crit", "Contact")})
	if len(r) != 1 {
		t.Errorf("CRIT escalation should bypass cooldown, got %d", len(r))
	}
}

func TestSuppressor_EpochWindowDropsDataRules(t *testing.T) {
	s := NewSuppressor(fakeWindow{inWindow: true}, 0, time.Hour)
	ts := time.Now()
	results := []types.RuleResult{
		resultWarn("data.row_spike_warn", "Contact"),
		resultWarn("app.goroutine_leak", ""),
		resultCrit("app.outbox_stall", ""),
		resultWarn("app.http_5xx_warn", ""),
	}
	r := s.Filter(ts, results)
	if len(r) != 2 {
		t.Fatalf("expected 2 survivors (app.goroutine_leak, app.http_5xx_warn), got %d", len(r))
	}
	if r[0].RuleID != "app.goroutine_leak" {
		t.Errorf("wrong first survivor: %s", r[0].RuleID)
	}
	if r[1].RuleID != "app.http_5xx_warn" {
		t.Errorf("wrong second survivor: %s", r[1].RuleID)
	}
}

func TestSuppressor_EpochWindowFalseKeepsAll(t *testing.T) {
	s := NewSuppressor(fakeWindow{inWindow: false}, 0, time.Hour)
	ts := time.Now()
	results := []types.RuleResult{
		resultWarn("data.row_spike_warn", "Contact"),
		resultWarn("app.goroutine_leak", ""),
	}
	r := s.Filter(ts, results)
	if len(r) != 2 {
		t.Errorf("window=false should pass everything, got %d", len(r))
	}
}
