package routing_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"

	"github.com/veylant/ia-gateway/internal/routing"
)

func makeRule(name, tenantID string, priority int) routing.RoutingRule {
	return routing.RoutingRule{
		Name:       name,
		TenantID:   tenantID,
		Priority:   priority,
		IsEnabled:  true,
		Conditions: []routing.Condition{},
		Action:     routing.Action{Provider: "openai"},
	}
}

func TestRuleCache_Get_LoadsFromStore(t *testing.T) {
	store := routing.NewMemStore()
	_, err := store.Create(context.Background(), makeRule("r1", "t1", 100))
	require.NoError(t, err)

	cache := routing.NewRuleCache(store, 30*time.Second, zap.NewNop())
	rules, err := cache.Get(context.Background(), "t1")
	require.NoError(t, err)
	assert.Len(t, rules, 1)
	assert.Equal(t, "r1", rules[0].Name)
}

func TestRuleCache_Get_ReturnsCachedOnSecondCall(t *testing.T) {
	store := routing.NewMemStore()
	_, err := store.Create(context.Background(), makeRule("r1", "t1", 100))
	require.NoError(t, err)

	cache := routing.NewRuleCache(store, 30*time.Second, zap.NewNop())
	// First call loads from store
	rules1, err := cache.Get(context.Background(), "t1")
	require.NoError(t, err)

	// Add another rule to the store — should NOT appear before cache expiry
	_, err = store.Create(context.Background(), makeRule("r2", "t1", 200))
	require.NoError(t, err)

	rules2, err := cache.Get(context.Background(), "t1")
	require.NoError(t, err)
	assert.Equal(t, len(rules1), len(rules2), "cache should serve stale data within TTL")
}

func TestRuleCache_Invalidate_ForcesReload(t *testing.T) {
	store := routing.NewMemStore()
	_, err := store.Create(context.Background(), makeRule("r1", "t1", 100))
	require.NoError(t, err)

	cache := routing.NewRuleCache(store, 30*time.Second, zap.NewNop())
	_, err = cache.Get(context.Background(), "t1")
	require.NoError(t, err)

	// Add a new rule and invalidate
	_, err = store.Create(context.Background(), makeRule("r2", "t1", 200))
	require.NoError(t, err)
	cache.Invalidate("t1")

	rules, err := cache.Get(context.Background(), "t1")
	require.NoError(t, err)
	assert.Len(t, rules, 2, "after invalidation, new rule should be visible")
}

func TestRuleCache_EmptyTenant_ReturnsEmpty(t *testing.T) {
	store := routing.NewMemStore()
	cache := routing.NewRuleCache(store, 30*time.Second, zap.NewNop())

	rules, err := cache.Get(context.Background(), "unknown-tenant")
	require.NoError(t, err)
	assert.Empty(t, rules)
}

func TestRuleCache_StartStop_NoRace(t *testing.T) {
	store := routing.NewMemStore()
	cache := routing.NewRuleCache(store, 10*time.Millisecond, zap.NewNop())
	cache.Start()
	// Perform some gets while refresh loop runs
	for i := 0; i < 5; i++ {
		_, _ = cache.Get(context.Background(), "t1")
		time.Sleep(5 * time.Millisecond)
	}
	cache.Stop()
}
