package packs

import (
	"context"
	"testing"

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

// Test 10: component round-trip — NewConnectorRateLimitComponent → ExpectedProjection shape match.
func TestNewConnectorRateLimitComponent_Valid(t *testing.T) {
	desc := "read-heavy endpoint"
	comp, err := NewConnectorRateLimitComponent("test-connector", connectorRateLimitDefinition{
		EndpointClass:     "crm.objects.read",
		RequestsPerSecond: 5.0,
		Burst:             10,
		Description:       &desc,
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if comp.Kind() != ComponentKindConnectorRateLimit {
		t.Errorf("Kind() = %q, want %q", comp.Kind(), ComponentKindConnectorRateLimit)
	}
	wantBK := "test-connector/crm.objects.read"
	if comp.BusinessKey() != wantBK {
		t.Errorf("BusinessKey() = %q, want %q", comp.BusinessKey(), wantBK)
	}
	if comp.Manageability() != ManageabilityLocked {
		t.Errorf("Manageability() = %q, want locked", comp.Manageability())
	}

	proj, err := comp.ExpectedProjection()
	if err != nil {
		t.Fatalf("ExpectedProjection: %v", err)
	}
	row, ok := proj.(connectorRateLimitProjectionRow)
	if !ok {
		t.Fatalf("projection type = %T, want connectorRateLimitProjectionRow", proj)
	}
	if row.OwnerPack != "test-connector" {
		t.Errorf("OwnerPack = %q, want \"test-connector\"", row.OwnerPack)
	}
	if row.EndpointClass != "crm.objects.read" {
		t.Errorf("EndpointClass = %q, want \"crm.objects.read\"", row.EndpointClass)
	}
	if row.RequestsPerSecond != 5.0 {
		t.Errorf("RequestsPerSecond = %v, want 5.0", row.RequestsPerSecond)
	}
	if row.Burst != 10 {
		t.Errorf("Burst = %d, want 10", row.Burst)
	}
}

// Validation: empty packName rejected.
func TestNewConnectorRateLimitComponent_EmptyPack(t *testing.T) {
	_, err := NewConnectorRateLimitComponent("", connectorRateLimitDefinition{
		EndpointClass: "class", RequestsPerSecond: 1, Burst: 1,
	})
	if err == nil {
		t.Fatal("expected error for empty packName")
	}
}

// Validation: empty endpointClass rejected.
func TestNewConnectorRateLimitComponent_EmptyEndpointClass(t *testing.T) {
	_, err := NewConnectorRateLimitComponent("test-pack", connectorRateLimitDefinition{
		EndpointClass: "", RequestsPerSecond: 1, Burst: 1,
	})
	if err == nil {
		t.Fatal("expected error for empty endpointClass")
	}
}

// Validation: rps <= 0 rejected.
func TestNewConnectorRateLimitComponent_InvalidRPS(t *testing.T) {
	_, err := NewConnectorRateLimitComponent("test-pack", connectorRateLimitDefinition{
		EndpointClass: "class", RequestsPerSecond: 0, Burst: 1,
	})
	if err == nil {
		t.Fatal("expected error for rps=0")
	}
}

// Validation: burst < 1 rejected.
func TestNewConnectorRateLimitComponent_InvalidBurst(t *testing.T) {
	_, err := NewConnectorRateLimitComponent("test-pack", connectorRateLimitDefinition{
		EndpointClass: "class", RequestsPerSecond: 1, Burst: 0,
	})
	if err == nil {
		t.Fatal("expected error for burst=0")
	}
}

// Test 10: Apply upserts row idempotently.
func TestConnectorRateLimitComponent_Apply_Upsert(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectExec(`INSERT INTO _System_ConnectorRateLimit`).
		WillReturnResult(sqlmock.NewResult(1, 1))

	comp, err := NewConnectorRateLimitComponent("test-pack", connectorRateLimitDefinition{
		EndpointClass:     "api.read",
		RequestsPerSecond: 10,
		Burst:             20,
	})
	if err != nil {
		t.Fatalf("constructor: %v", err)
	}

	deps := ApplyDeps{DB: db}
	if err := comp.Apply(context.Background(), deps); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("unmet mock expectations: %v", err)
	}
}

// Test 10: LiveProjection reads back the stored row shape.
func TestConnectorRateLimitComponent_LiveProjection(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(`SELECT OwnerPack, EndpointClass, RequestsPerSecond, Burst, Description`).
		WithArgs("test-pack", "api.read").
		WillReturnRows(sqlmock.NewRows([]string{"OwnerPack", "EndpointClass", "RequestsPerSecond", "Burst", "Description"}).
			AddRow("test-pack", "api.read", 10.0, 20, nil))

	comp, err := NewConnectorRateLimitComponent("test-pack", connectorRateLimitDefinition{
		EndpointClass:     "api.read",
		RequestsPerSecond: 10,
		Burst:             20,
	})
	if err != nil {
		t.Fatalf("constructor: %v", err)
	}

	deps := VerifyDeps{DB: db}
	live, err := comp.LiveProjection(context.Background(), deps)
	if err != nil {
		t.Fatalf("LiveProjection: %v", err)
	}
	row, ok := live.(connectorRateLimitProjectionRow)
	if !ok {
		t.Fatalf("live projection type = %T", live)
	}
	if row.EndpointClass != "api.read" {
		t.Errorf("EndpointClass = %q, want \"api.read\"", row.EndpointClass)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("unmet mock expectations: %v", err)
	}
}
