package connector_throttle

import (
	"context"
	"testing"

	sqlmock "github.com/DATA-DOG/go-sqlmock"
)

// NoopLimitProvider returns ok=false for every input; the throttle Service then
// applies DefaultRate/DefaultBurst. It is the test/fallback provider used when
// no DB-backed _System_ConnectorRateLimit reader is wired.
func TestNoopLimitProvider_AlwaysReturnsFalse(t *testing.T) {
	p := NewNoopLimitProvider()

	cases := []struct {
		name, namespace, endpointClass string
	}{
		{"empty namespace empty endpoint", "", ""},
		{"empty namespace populated endpoint", "", "crm.objects.read"},
		{"populated namespace empty endpoint", "test-pack", ""},
		{"populated namespace populated endpoint", "test-pack", "crm.objects.read"},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			rps, burst, ok, err := p.LimitFor(context.Background(), tc.namespace, tc.endpointClass)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if ok {
				t.Errorf("expected ok=false; got rps=%v burst=%d ok=true", rps, burst)
			}
			if rps != 0 || burst != 0 {
				t.Errorf("expected rps=0 burst=0 with ok=false; got rps=%v burst=%d", rps, burst)
			}
		})
	}
}

// SQL provider: a live row returns its policy.
func TestSQLLimitProvider_RowPresent(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(`SELECT RequestsPerSecond, Burst FROM _System_ConnectorRateLimit`).
		WithArgs("test-pack", "crm.objects.read").
		WillReturnRows(sqlmock.NewRows([]string{"RequestsPerSecond", "Burst"}).AddRow(5.0, 10))

	rps, burst, ok, err := NewSQLLimitProvider(db).LimitFor(context.Background(), "test-pack", "crm.objects.read")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !ok || rps != 5.0 || burst != 10 {
		t.Errorf("got (rps=%v, burst=%d, ok=%v), want (5, 10, true)", rps, burst, ok)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("unmet mock expectations: %v", err)
	}
}

// SQL provider: no row → ok=false (caller falls back to default).
func TestSQLLimitProvider_NoRow_ReturnsFalse(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(`SELECT RequestsPerSecond, Burst FROM _System_ConnectorRateLimit`).
		WithArgs("test-pack", "unknown.class").
		WillReturnRows(sqlmock.NewRows([]string{"RequestsPerSecond", "Burst"}))

	_, _, ok, err := NewSQLLimitProvider(db).LimitFor(context.Background(), "test-pack", "unknown.class")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if ok {
		t.Error("expected ok=false for missing row, got true")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("unmet mock expectations: %v", err)
	}
}

// Regression: a mixed-case namespace / endpointClass must be lower-trimmed to
// match the canonical OwnerPack / EndpointClass form connector_rate_limit_component.go
// writes, or a mixed-case connector pack silently misses its authored policy and
// falls back to default quota (codex Review PR #1537).
func TestSQLLimitProvider_NormalizesNamespaceAndEndpointClass(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	// The query MUST use the lowered/trimmed args, not the raw mixed-case input.
	mock.ExpectQuery(`SELECT RequestsPerSecond, Burst FROM _System_ConnectorRateLimit`).
		WithArgs("acme-connector", "crm.objects.read").
		WillReturnRows(sqlmock.NewRows([]string{"RequestsPerSecond", "Burst"}).AddRow(3.0, 6))

	rps, burst, ok, err := NewSQLLimitProvider(db).LimitFor(context.Background(), "  Acme-Connector ", "  CRM.Objects.Read  ")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !ok || rps != 3.0 || burst != 6 {
		t.Errorf("got (rps=%v, burst=%d, ok=%v), want (3, 6, true) — mixed-case must normalize to the stored row", rps, burst, ok)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("unmet mock expectations: %v", err)
	}
}
