//go:build integration

package metadata

import (
	"context"
	"testing"

	"github.com/nexuscrm/backend/internal/features/events"
	"github.com/nexuscrm/backend/internal/infrastructure/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMetadataCache_FlowCaching(t *testing.T) {
	conn, ms := SetupIntegrationTest(t)
	_ = conn // conn used by ms setup

	ctx := context.Background()

	t.Run("GetFlowDefinitions_uses_cache", func(t *testing.T) {
		// Initial call should load cache
		flows1 := ms.GetFlowDefinitions(ctx)

		// Second call should use cache (same result)
		flows2 := ms.GetFlowDefinitions(ctx)

		// Results should be identical (same slice reference if cached)
		assert.Equal(t, len(flows1), len(flows2), "Flow definition counts should match")
	})

	t.Run("CreateFlowDefinition_invalidates_cache", func(t *testing.T) {
		// Get initial flow definitions
		initialFlows := ms.GetFlowDefinitions(ctx)
		initialCount := len(initialFlows)

		// Create a new flow definition
		newFlow := &models.FlowDefinition{
			MasterLabel:          "Test Cache Flow",
			DeveloperName:        "Test_Cache_Flow",
			TriggerObjectOrEvent: "account",
			TriggerType:          constants.TriggerAfterCreate,
			ProcessType:          "AutoLaunchedFlow",
		}
		err := ms.CreateFlowDefinition(ctx, newFlow)
		require.NoError(t, err, "CreateFlowDefinition should succeed")

		// Cleanup
		t.Cleanup(func() {
			_ = ms.DeleteFlowDefinition(ctx, newFlow.Id)
		})

		// Cache should be invalidated, next call should include new flow
		updatedFlows := ms.GetFlowDefinitions(ctx)
		assert.Equal(t, initialCount+1, len(updatedFlows), "Flow definition count should increase by 1 after CreateFlowDefinition")
	})

	t.Run("DeleteFlowDefinition_invalidates_cache", func(t *testing.T) {
		// Create a flow definition to delete
		newFlow := &models.FlowDefinition{
			MasterLabel:          "Test Delete Flow",
			DeveloperName:        "Test_Delete_Flow",
			TriggerObjectOrEvent: "account",
			TriggerType:          constants.TriggerAfterUpdate,
			ProcessType:          "AutoLaunchedFlow",
		}
		err := ms.CreateFlowDefinition(ctx, newFlow)
		require.NoError(t, err, "CreateFlowDefinition should succeed")

		// Get count after creation
		countAfterCreate := len(ms.GetFlowDefinitions(ctx))

		// Delete the flow definition
		err = ms.DeleteFlowDefinition(ctx, newFlow.Id)
		require.NoError(t, err, "DeleteFlowDefinition should succeed")

		// Cache should be invalidated
		countAfterDelete := len(ms.GetFlowDefinitions(ctx))
		assert.Equal(t, countAfterCreate-1, countAfterDelete, "Flow definition count should decrease after delete")
	})
}

func TestMetadataCache_ValidationRuleCaching(t *testing.T) {
	conn, ms := SetupIntegrationTest(t)
	_ = conn

	ctx := context.Background()

	// Create a unique test object for this test
	testObject := "test_validation_cache_obj"
	objDef := models.ObjectMetadata{
		APIName: testObject,
		Label:   "Test Validation Cache Object",
		Fields:  []models.FieldMetadata{
			// Name is a standard field, adding it explicitly causes duplicate column error
		},
	}
	err := ms.CreateSchemaOptimized(ctx, &objDef)
	require.NoError(t, err, "Failed to create test object")

	// Cleanup after test
	t.Cleanup(func() {
		_ = ms.DeleteSchema(ctx, testObject)
	})

	t.Run("GetValidationRules_uses_cache", func(t *testing.T) {
		// Initial call should load cache
		rules1 := ms.GetValidationRules(ctx, testObject)

		// Second call should use cache
		rules2 := ms.GetValidationRules(ctx, testObject)

		// Results should be identical
		assert.Equal(t, len(rules1), len(rules2), "Validation rule counts should match")
	})

	t.Run("CreateValidationRule_invalidates_cache", func(t *testing.T) {
		// Get initial rules
		initialRules := ms.GetValidationRules(ctx, testObject)
		initialCount := len(initialRules)

		// Create a new validation rule
		newRule := &models.ValidationRule{
			ObjectAPIName: testObject,
			Name:          "Test Cache Validation Rule",
			Condition:     "Name == \"\"",
			ErrorMessage:  "Name is required for cache test",
			Active:        true,
		}
		err := ms.CreateValidationRule(ctx, newRule)
		require.NoError(t, err, "CreateValidationRule should succeed")

		// Cleanup
		t.Cleanup(func() {
			_ = ms.DeleteValidationRule(ctx, newRule.ID)
		})

		// Cache should be invalidated
		updatedRules := ms.GetValidationRules(ctx, testObject)
		assert.Equal(t, initialCount+1, len(updatedRules), "Validation rule count should increase by 1")
	})

	t.Run("GetValidationRules_skips_system_tables", func(t *testing.T) {
		// System tables should always return empty (optimization)
		rules := ms.GetValidationRules(ctx, constants.TableUser)
		assert.Empty(t, rules, "System tables should return empty validation rules")
	})
}

func TestMetadataCache_AutoNumberCaching(t *testing.T) {
	conn, ms := SetupIntegrationTest(t)
	_ = conn

	ctx := context.Background()

	t.Run("GetAutoNumbers_uses_cache", func(t *testing.T) {
		// Initial call should load cache
		ans1 := ms.GetAutoNumbers(ctx, "account")

		// Second call should use cache
		ans2 := ms.GetAutoNumbers(ctx, "account")

		// Results should be identical
		assert.Equal(t, len(ans1), len(ans2), "AutoNumber counts should match")
	})

	t.Run("GetAutoNumbers_returns_empty_for_unknown_object", func(t *testing.T) {
		// Unknown objects should return empty slice (from cache)
		ans := ms.GetAutoNumbers(ctx, "nonexistent_object_xyz")
		assert.Empty(t, ans, "Non-existent objects should return empty auto-numbers")
	})
}

func TestMetadataCache_IdentifierRuleCaching(t *testing.T) {
	conn, ms := SetupIntegrationTest(t)
	_ = conn

	ctx := context.Background()

	t.Run("GetIdentifierRule_uses_cache", func(t *testing.T) {
		// Identifier rules are seeded by platform_config pack (Task 1).
		// We can't assume specific rules exist on every test DB, but the
		// call must not error and must be idempotent.
		rule1, err1 := ms.GetIdentifierRule(ctx, "Quote")
		require.NoError(t, err1)

		rule2, err2 := ms.GetIdentifierRule(ctx, "Quote")
		require.NoError(t, err2)

		// Same cache snapshot must yield the exact same pointer (or both
		// nil). require.Same checks pointer identity, which is the strong
		// proof of a cache hit — deep equality (assert.Equal) would also
		// pass for an accidentally re-loaded copy and miss the regression.
		require.Same(t, rule1, rule2, "repeated reads must return the cached pointer, not a re-loaded copy")
	})

	t.Run("GetIdentifierRule_returns_nil_for_unknown_object", func(t *testing.T) {
		got, err := ms.GetIdentifierRule(ctx, "nonexistent_object_xyz")
		require.NoError(t, err)
		assert.Nil(t, got, "objects without an active rule must yield (nil, nil)")
	})
}

func TestMetadataCache_RefreshAndInvalidate(t *testing.T) {
	conn, _ := SetupIntegrationTest(t)
	db := conn.DB()

	// Services
	repo := persistence.NewMetadataRepository(db)
	schemaRepo := persistence.NewSchemaRepository(db, nil)
	schemaMgr := NewSchemaManager(schemaRepo)
	eventBus := events.NewEventBus()
	ms := NewMetadataService(repo, schemaMgr, eventBus)

	t.Run("RefreshCache_loads_all_caches", func(t *testing.T) {
		// Refresh cache
		err := ms.RefreshCache(context.Background())
		require.NoError(t, err, "RefreshCache should succeed")

		// Verify caches are populated
		ctx := context.Background()

		// Schemas should be loaded
		schemas := ms.GetSchemas(ctx)
		assert.NotEmpty(t, schemas, "Schemas should be loaded after RefreshCache")

		// Flows should be accessible (may be empty)
		flows := ms.GetFlowDefinitions(ctx)
		t.Logf("Loaded %d flows", len(flows))
	})

	t.Run("InvalidateCache_clears_caches", func(t *testing.T) {
		// First ensure cache is loaded
		_ = ms.RefreshCache(context.Background())

		// Invalidate
		ms.InvalidateCache()

		// Next call should re-load cache
		ctx := context.Background()
		schemas := ms.GetSchemas(ctx)
		assert.NotEmpty(t, schemas, "Schemas should be reloaded after InvalidateCache + GetSchemas")
	})
}
