package fn_operations

import (
	"os"
	"path/filepath"
	"testing"
	"time"
)

func tempDB(t *testing.T) *DB {
	t.Helper()
	path := filepath.Join(t.TempDir(), "test.db")
	db, err := Open(path)
	if err != nil {
		t.Fatalf("opening test db: %v", err)
	}
	t.Cleanup(func() { db.Close() })
	return db
}

func TestOpenAndClose(t *testing.T) {
	path := filepath.Join(t.TempDir(), "test.db")
	db, err := Open(path)
	if err != nil {
		t.Fatalf("open: %v", err)
	}
	if err := db.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}
	if _, err := os.Stat(path); os.IsNotExist(err) {
		t.Fatal("db file should exist")
	}
}

func TestTypeSnapshotCRUD(t *testing.T) {
	db := tempDB(t)

	ts := &TypeSnapshot{
		ID:          "ohlcv_go_finance",
		Version:     "1.0.0",
		Lang:        "go",
		Algebraic:   "product",
		Definition:  "type OHLCV struct { ... }",
		Description: "Vela de mercado",
	}

	if err := db.InsertTypeSnapshot(ts); err != nil {
		t.Fatalf("insert: %v", err)
	}

	got, err := db.GetTypeSnapshot("ohlcv_go_finance")
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got == nil {
		t.Fatal("expected snapshot, got nil")
	}
	if got.Definition != ts.Definition {
		t.Errorf("definition = %q, want %q", got.Definition, ts.Definition)
	}

	// INSERT OR IGNORE: second insert should not overwrite
	ts2 := &TypeSnapshot{
		ID:          "ohlcv_go_finance",
		Version:     "2.0.0",
		Lang:        "go",
		Algebraic:   "product",
		Definition:  "type OHLCV struct { CHANGED }",
		Description: "Changed",
	}
	if err := db.InsertTypeSnapshot(ts2); err != nil {
		t.Fatalf("insert duplicate: %v", err)
	}
	got2, _ := db.GetTypeSnapshot("ohlcv_go_finance")
	if got2.Version != "1.0.0" {
		t.Errorf("snapshot should be immutable, got version %q", got2.Version)
	}

	// Not found
	missing, err := db.GetTypeSnapshot("nonexistent")
	if err != nil {
		t.Fatalf("get missing: %v", err)
	}
	if missing != nil {
		t.Error("expected nil for missing snapshot")
	}

	all, err := db.ListTypeSnapshots()
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(all) != 1 {
		t.Errorf("expected 1 snapshot, got %d", len(all))
	}
}

func TestEntityCRUD(t *testing.T) {
	db := tempDB(t)

	// Insert snapshot first (type_ref)
	db.InsertTypeSnapshot(&TypeSnapshot{
		ID: "tick_go_finance", Version: "1.0.0", Lang: "go", Algebraic: "product",
	})

	e := &Entity{
		ID:      "ticks_btcusdt_2024",
		Name:    "ticks_btcusdt_2024",
		TypeRef: "tick_go_finance",
		Status:  StatusActive,
		Source:  "binance_api",
		Domain:  "market_data",
		Tags:    []string{"btc", "binance"},
		Metadata: map[string]any{
			"pair":     "BTCUSDT",
			"exchange": "binance",
		},
	}

	if err := db.InsertEntity(e); err != nil {
		t.Fatalf("insert: %v", err)
	}

	got, err := db.GetEntity("ticks_btcusdt_2024")
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got == nil {
		t.Fatal("expected entity, got nil")
	}
	if got.Source != "binance_api" {
		t.Errorf("source = %q, want binance_api", got.Source)
	}
	if len(got.Tags) != 2 {
		t.Errorf("tags len = %d, want 2", len(got.Tags))
	}
	if got.Metadata["pair"] != "BTCUSDT" {
		t.Errorf("metadata pair = %v, want BTCUSDT", got.Metadata["pair"])
	}

	// Update
	got.Status = StatusStale
	if err := db.UpdateEntity(got); err != nil {
		t.Fatalf("update: %v", err)
	}
	updated, _ := db.GetEntity("ticks_btcusdt_2024")
	if updated.Status != StatusStale {
		t.Errorf("status = %q, want stale", updated.Status)
	}

	// List
	all, err := db.ListEntities("market_data", "")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(all) != 1 {
		t.Errorf("expected 1, got %d", len(all))
	}

	// Search
	found, err := db.SearchEntities("btcusdt", "")
	if err != nil {
		t.Fatalf("search: %v", err)
	}
	if len(found) != 1 {
		t.Errorf("search expected 1, got %d", len(found))
	}

	// Delete
	if err := db.DeleteEntity("ticks_btcusdt_2024"); err != nil {
		t.Fatalf("delete: %v", err)
	}
	deleted, _ := db.GetEntity("ticks_btcusdt_2024")
	if deleted != nil {
		t.Error("expected nil after delete")
	}
}

func TestRelationCRUD(t *testing.T) {
	db := tempDB(t)

	// Setup entities
	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "a", Name: "a", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertEntity(&Entity{ID: "b", Name: "b", TypeRef: "t1", Status: StatusActive, Source: "test"})

	r := &Relation{
		ID:        "a__to__b__via__transform",
		Name:      "TRANSFORMA",
		FromEntity: "a",
		ToEntity:  "b",
		Direction: DirUnidirectional,
		Status:    RelDesigned,
	}

	if err := InsertRelationSafe(db, r); err != nil {
		t.Fatalf("insert relation: %v", err)
	}

	got, err := db.GetRelation("a__to__b__via__transform")
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got == nil {
		t.Fatal("expected relation, got nil")
	}
	if got.Name != "TRANSFORMA" {
		t.Errorf("name = %q, want TRANSFORMA", got.Name)
	}

	// List by entity
	rels, err := db.ListRelations("a")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(rels) != 1 {
		t.Errorf("expected 1, got %d", len(rels))
	}

	// Delete
	if err := db.DeleteRelation("a__to__b__via__transform"); err != nil {
		t.Fatalf("delete: %v", err)
	}
	deleted, _ := db.GetRelation("a__to__b__via__transform")
	if deleted != nil {
		t.Error("expected nil after delete")
	}
}

func TestRelationInputs(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "a", Name: "a", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertEntity(&Entity{ID: "b", Name: "b", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertEntity(&Entity{ID: "c", Name: "c", TypeRef: "t1", Status: StatusActive, Source: "test"})

	r := &Relation{
		ID:       "multi__to__c",
		Name:     "ENRIQUECE",
		ToEntity: "c",
		Direction: DirUnidirectional,
		Status:   RelDesigned,
	}

	inputs := []RelationInput{
		{ID: "i1", RelationID: "multi__to__c", EntityID: "a", Role: "base"},
		{ID: "i2", RelationID: "multi__to__c", EntityID: "b", Role: "lookup"},
	}

	if err := InsertRelationWithInputs(db, r, inputs); err != nil {
		t.Fatalf("insert with inputs: %v", err)
	}

	got, err := db.GetRelationInputs("multi__to__c")
	if err != nil {
		t.Fatalf("get inputs: %v", err)
	}
	if len(got) != 2 {
		t.Errorf("expected 2 inputs, got %d", len(got))
	}
}

func TestCycleDetectionCausal(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "a", Name: "a", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertEntity(&Entity{ID: "b", Name: "b", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertEntity(&Entity{ID: "c", Name: "c", TypeRef: "t1", Status: StatusActive, Source: "test"})

	// a -> b (causal)
	InsertRelationSafe(db, &Relation{
		ID: "ab", Name: "T1", FromEntity: "a", ToEntity: "b", Via: "fn1",
		Purity: "impure", Direction: DirUnidirectional, Status: RelDesigned,
	})

	// b -> c (causal)
	InsertRelationSafe(db, &Relation{
		ID: "bc", Name: "T2", FromEntity: "b", ToEntity: "c", Via: "fn2",
		Purity: "impure", Direction: DirUnidirectional, Status: RelDesigned,
	})

	// c -> a (causal) should fail — creates cycle
	err := InsertRelationSafe(db, &Relation{
		ID: "ca", Name: "T3", FromEntity: "c", ToEntity: "a", Via: "fn3",
		Purity: "impure", Direction: DirUnidirectional, Status: RelDesigned,
	})
	if err == nil {
		t.Fatal("expected cycle error, got nil")
	}
}

func TestCycleDetectionSemanticAllowed(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "juan", Name: "juan", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertEntity(&Entity{ID: "paula", Name: "paula", TypeRef: "t1", Status: StatusActive, Source: "test"})

	// juan -> paula (semantic, no via)
	if err := InsertRelationSafe(db, &Relation{
		ID: "jp", Name: "CONOCE A", FromEntity: "juan", ToEntity: "paula",
		Direction: DirBidirectional, Status: RelRunning,
	}); err != nil {
		t.Fatalf("insert semantic: %v", err)
	}

	// paula -> juan (semantic, no via) — should succeed, no cycle check
	if err := InsertRelationSafe(db, &Relation{
		ID: "pj", Name: "CONOCE A", FromEntity: "paula", ToEntity: "juan",
		Direction: DirBidirectional, Status: RelRunning,
	}); err != nil {
		t.Fatalf("semantic cycle should be allowed: %v", err)
	}
}

func TestValidateEntity(t *testing.T) {
	tests := []struct {
		name    string
		entity  Entity
		wantErr bool
	}{
		{
			name:    "valid",
			entity:  Entity{ID: "x", Name: "x", TypeRef: "t1", Status: StatusActive, Source: "test"},
			wantErr: false,
		},
		{
			name:    "missing name",
			entity:  Entity{ID: "x", TypeRef: "t1", Status: StatusActive, Source: "test"},
			wantErr: true,
		},
		{
			name:    "missing source",
			entity:  Entity{ID: "x", Name: "x", TypeRef: "t1", Status: StatusActive},
			wantErr: true,
		},
		{
			name:    "missing type_ref",
			entity:  Entity{ID: "x", Name: "x", Status: StatusActive, Source: "test"},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateEntity(&tt.entity)
			if (err != nil) != tt.wantErr {
				t.Errorf("ValidateEntity() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestExecutionCRUD(t *testing.T) {
	db := tempDB(t)

	started := time.Date(2026, 3, 28, 10, 0, 0, 0, time.UTC)
	ended := time.Date(2026, 3, 28, 10, 5, 0, 0, time.UTC)
	rin := int64(1000)
	rout := int64(60)

	e := &Execution{
		ID:         "exec_1",
		PipelineID: "tick_to_ohlcv_go_finance",
		RelationID: "rel_1",
		Status:     ExecSuccess,
		StartedAt:  started,
		EndedAt:    &ended,
		RecordsIn:  &rin,
		RecordsOut: &rout,
		Metrics:    map[string]any{"mean_close": 42000},
	}

	if err := InsertExecutionSafe(db, e); err != nil {
		t.Fatalf("insert: %v", err)
	}

	// Auto-calculated duration
	if e.DurationMs == nil || *e.DurationMs != 300000 {
		t.Errorf("duration_ms = %v, want 300000", e.DurationMs)
	}

	got, err := db.GetExecution("exec_1")
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got == nil {
		t.Fatal("expected execution, got nil")
	}
	if got.PipelineID != "tick_to_ohlcv_go_finance" {
		t.Errorf("pipeline_id = %q", got.PipelineID)
	}
	if got.Metrics["mean_close"] != float64(42000) {
		t.Errorf("metrics mean_close = %v", got.Metrics["mean_close"])
	}

	// List
	all, err := db.ListExecutions("", "", "")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	if len(all) != 1 {
		t.Errorf("list = %d, want 1", len(all))
	}

	byPipeline, _ := db.ListExecutions("tick_to_ohlcv_go_finance", "", "")
	if len(byPipeline) != 1 {
		t.Errorf("list by pipeline = %d, want 1", len(byPipeline))
	}

	byStatus, _ := db.ListExecutions("", "", ExecFailure)
	if len(byStatus) != 0 {
		t.Errorf("list by failure status = %d, want 0", len(byStatus))
	}
}

func TestAssertionCRUD(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test",
		Metadata: map[string]any{"close": 120, "high": 150, "low": 90}})

	a := &Assertion{
		ID:       "assert_close_positive",
		EntityID: "e1",
		Name:     "close positivo",
		Kind:     "range",
		Rule:     "close > 0",
		Severity: SeverityCritical,
		Active:   true,
	}

	if err := InsertAssertionSafe(db, a); err != nil {
		t.Fatalf("insert: %v", err)
	}

	got, err := db.GetAssertion("assert_close_positive")
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got == nil {
		t.Fatal("expected assertion, got nil")
	}
	if got.Rule != "close > 0" {
		t.Errorf("rule = %q", got.Rule)
	}
	if !got.Active {
		t.Error("expected active = true")
	}

	// Update
	got.Active = false
	if err := db.UpdateAssertion(got); err != nil {
		t.Fatalf("update: %v", err)
	}
	updated, _ := db.GetAssertion("assert_close_positive")
	if updated.Active {
		t.Error("expected active = false after update")
	}

	// List with active filter
	active := true
	byActive, _ := db.ListAssertions("e1", &active)
	if len(byActive) != 0 {
		t.Errorf("list active = %d, want 0 (we deactivated it)", len(byActive))
	}

	// Search FTS
	updated.Active = true
	db.UpdateAssertion(updated)
	found, err := db.SearchAssertions("close", "")
	if err != nil {
		t.Fatalf("search: %v", err)
	}
	if len(found) != 1 {
		t.Errorf("search 'close' = %d, want 1", len(found))
	}

	// Delete
	if err := db.DeleteAssertion("assert_close_positive"); err != nil {
		t.Fatalf("delete: %v", err)
	}
	deleted, _ := db.GetAssertion("assert_close_positive")
	if deleted != nil {
		t.Error("expected nil after delete")
	}
}

func TestAssertionResultCRUD(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertAssertion(&Assertion{ID: "a1", EntityID: "e1", Name: "test", Kind: "range", Rule: "x > 0", Severity: SeverityWarning, Active: true})

	ar := &AssertionResult{
		ID:          "ar_1",
		AssertionID: "a1",
		ExecutionID: "exec_1",
		Status:      ResultFail,
		Value:       map[string]any{"x": -5},
		Message:     "rule failed: x > 0",
		EvaluatedAt: time.Now().UTC(),
	}

	if err := db.InsertAssertionResult(ar); err != nil {
		t.Fatalf("insert: %v", err)
	}

	got, err := db.GetAssertionResult("ar_1")
	if err != nil {
		t.Fatalf("get: %v", err)
	}
	if got == nil {
		t.Fatal("expected result, got nil")
	}
	if got.Status != ResultFail {
		t.Errorf("status = %q, want fail", got.Status)
	}

	// List by assertion
	byAssertion, _ := db.ListAssertionResults("a1", "")
	if len(byAssertion) != 1 {
		t.Errorf("list by assertion = %d, want 1", len(byAssertion))
	}

	// List by execution
	byExec, _ := db.ListAssertionResults("", "exec_1")
	if len(byExec) != 1 {
		t.Errorf("list by execution = %d, want 1", len(byExec))
	}
}

func TestEvalAssertion(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{
		ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test",
		Metadata: map[string]any{"close": 120, "high": 150, "low": 90, "open": 100},
	})

	tests := []struct {
		name       string
		rule       string
		kind       string
		wantStatus AssertionResultStatus
	}{
		{"range pass", "close > 0 AND close < 1000000", "range", ResultPass},
		{"range fail", "close > 200", "range", ResultFail},
		{"consistency pass", "low <= close AND close <= high", "consistency", ResultPass},
		{"consistency fail", "low > close", "consistency", ResultFail},
		{"null pass (field exists)", "close IS NOT NULL", "null", ResultPass},
		{"json_extract direct", "json_extract(metadata, '$.close') > 0", "range", ResultPass},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			a := &Assertion{
				ID: "test_" + tt.name, EntityID: "e1", Name: tt.name,
				Kind: tt.kind, Rule: tt.rule, Severity: SeverityWarning, Active: true,
			}
			result, err := EvalAssertion(db, a, "")
			if err != nil {
				t.Fatalf("eval: %v", err)
			}
			if result.Status != tt.wantStatus {
				t.Errorf("status = %q, want %q (message: %s)", result.Status, tt.wantStatus, result.Message)
			}
		})
	}
}

func TestEvalEntityAssertions(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{
		ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test",
		Metadata: map[string]any{"close": 120},
	})

	db.InsertAssertion(&Assertion{ID: "a1", EntityID: "e1", Name: "pass", Kind: "range", Rule: "close > 0", Severity: SeverityWarning, Active: true})
	db.InsertAssertion(&Assertion{ID: "a2", EntityID: "e1", Name: "fail", Kind: "range", Rule: "close > 200", Severity: SeverityCritical, Active: true})
	db.InsertAssertion(&Assertion{ID: "a3", EntityID: "e1", Name: "inactive", Kind: "range", Rule: "close > 999", Severity: SeverityInfo, Active: false})

	results, err := EvalEntityAssertions(db, "e1", "exec_test")
	if err != nil {
		t.Fatalf("eval: %v", err)
	}

	// Should only eval active assertions (a1, a2), not a3
	if len(results) != 2 {
		t.Fatalf("expected 2 results, got %d", len(results))
	}

	pass, fail := 0, 0
	for _, r := range results {
		switch r.Status {
		case ResultPass:
			pass++
		case ResultFail:
			fail++
		}
		if r.ExecutionID != "exec_test" {
			t.Errorf("execution_id = %q, want exec_test", r.ExecutionID)
		}
	}
	if pass != 1 || fail != 1 {
		t.Errorf("pass=%d fail=%d, want 1 and 1", pass, fail)
	}

	// Results should be persisted
	stored, _ := db.ListAssertionResults("", "exec_test")
	if len(stored) != 2 {
		t.Errorf("stored results = %d, want 2", len(stored))
	}
}

func TestReactCriticalCorruptsEntity(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{
		ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test",
		Metadata: map[string]any{"close": 120},
	})
	db.InsertAssertion(&Assertion{ID: "a_crit", EntityID: "e1", Name: "critical_fail", Kind: "range", Rule: "close > 200", Severity: SeverityCritical, Active: true})

	// Simulate failed assertion result
	results := []AssertionResult{
		{ID: "ar1", AssertionID: "a_crit", Status: ResultFail, Message: "close > 200 failed", EvaluatedAt: time.Now().UTC()},
	}

	rr, err := React(db, nil, results)
	if err != nil {
		t.Fatalf("react: %v", err)
	}

	if len(rr.EntityUpdates) != 1 {
		t.Fatalf("expected 1 entity update, got %d", len(rr.EntityUpdates))
	}
	if rr.EntityUpdates[0].NewStatus != StatusCorrupted {
		t.Errorf("new status = %q, want corrupted", rr.EntityUpdates[0].NewStatus)
	}

	// Verify entity in DB
	entity, _ := db.GetEntity("e1")
	if entity.Status != StatusCorrupted {
		t.Errorf("entity status = %q, want corrupted", entity.Status)
	}
}

func TestReactWarningStalesEntity(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{
		ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test",
		Metadata: map[string]any{"close": 120},
	})
	db.InsertAssertion(&Assertion{ID: "a_warn", EntityID: "e1", Name: "warn_fail", Kind: "range", Rule: "close > 200", Severity: SeverityWarning, Active: true})

	results := []AssertionResult{
		{ID: "ar1", AssertionID: "a_warn", Status: ResultFail, Message: "warning", EvaluatedAt: time.Now().UTC()},
	}

	rr, err := React(db, nil, results)
	if err != nil {
		t.Fatalf("react: %v", err)
	}

	if len(rr.EntityUpdates) != 1 {
		t.Fatalf("expected 1 entity update, got %d", len(rr.EntityUpdates))
	}
	if rr.EntityUpdates[0].NewStatus != StatusStale {
		t.Errorf("new status = %q, want stale", rr.EntityUpdates[0].NewStatus)
	}
}

func TestReactWarningDoesNotDowngradeCorrupted(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{
		ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusCorrupted, Source: "test",
	})
	db.InsertAssertion(&Assertion{ID: "a_warn", EntityID: "e1", Name: "warn", Kind: "range", Rule: "x > 0", Severity: SeverityWarning, Active: true})

	results := []AssertionResult{
		{ID: "ar1", AssertionID: "a_warn", Status: ResultFail, EvaluatedAt: time.Now().UTC()},
	}

	rr, err := React(db, nil, results)
	if err != nil {
		t.Fatalf("react: %v", err)
	}

	// Warning should NOT change corrupted entity to stale
	if len(rr.EntityUpdates) != 0 {
		t.Errorf("expected no entity updates, got %d", len(rr.EntityUpdates))
	}
}

func TestReactInfoNoStatusChange(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{
		ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test",
	})
	db.InsertAssertion(&Assertion{ID: "a_info", EntityID: "e1", Name: "info", Kind: "range", Rule: "x > 0", Severity: SeverityInfo, Active: true})

	results := []AssertionResult{
		{ID: "ar1", AssertionID: "a_info", Status: ResultFail, EvaluatedAt: time.Now().UTC()},
	}

	rr, err := React(db, nil, results)
	if err != nil {
		t.Fatalf("react: %v", err)
	}

	if len(rr.EntityUpdates) != 0 {
		t.Errorf("info fail should not change status, got %d updates", len(rr.EntityUpdates))
	}
}

func TestValidateExecution(t *testing.T) {
	tests := []struct {
		name    string
		e       Execution
		wantErr bool
	}{
		{"valid", Execution{ID: "e1", PipelineID: "p1", Status: ExecSuccess, StartedAt: time.Now()}, false},
		{"missing id", Execution{PipelineID: "p1", Status: ExecSuccess, StartedAt: time.Now()}, true},
		{"missing pipeline", Execution{ID: "e1", Status: ExecSuccess, StartedAt: time.Now()}, true},
		{"missing status", Execution{ID: "e1", PipelineID: "p1", StartedAt: time.Now()}, true},
		{"invalid status", Execution{ID: "e1", PipelineID: "p1", Status: "invalid", StartedAt: time.Now()}, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateExecution(&tt.e)
			if (err != nil) != tt.wantErr {
				t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestValidateAssertion(t *testing.T) {
	known := map[string]bool{"e1": true}
	tests := []struct {
		name    string
		a       Assertion
		wantErr bool
	}{
		{"valid", Assertion{ID: "a1", EntityID: "e1", Name: "test", Kind: "range", Rule: "x > 0", Severity: SeverityWarning}, false},
		{"missing entity", Assertion{ID: "a1", EntityID: "unknown", Name: "test", Kind: "range", Rule: "x > 0", Severity: SeverityWarning}, true},
		{"missing rule", Assertion{ID: "a1", EntityID: "e1", Name: "test", Kind: "range", Severity: SeverityWarning}, true},
		{"missing kind", Assertion{ID: "a1", EntityID: "e1", Name: "test", Rule: "x > 0", Severity: SeverityWarning}, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateAssertion(&tt.a, known)
			if (err != nil) != tt.wantErr {
				t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestMigrations(t *testing.T) {
	db := tempDB(t)

	var count int
	err := db.conn.QueryRow("SELECT COUNT(*) FROM schema_migrations").Scan(&count)
	if err != nil {
		t.Fatalf("query: %v", err)
	}
	if count < 2 {
		t.Errorf("expected at least 2 migrations, got %d", count)
	}

	// Verify all new tables exist
	for _, table := range []string{"executions", "assertions", "assertion_results"} {
		_, err := db.conn.Exec("SELECT 1 FROM " + table + " LIMIT 1")
		if err != nil {
			t.Errorf("table %s should exist: %v", table, err)
		}
	}
}

func TestRecordExecutionWithResults(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "e1", Name: "e1", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertAssertion(&Assertion{ID: "a1", EntityID: "e1", Name: "test", Kind: "range", Rule: "x > 0", Severity: SeverityWarning, Active: true})

	started := time.Now().UTC()
	ended := started.Add(5 * time.Second)
	e := &Execution{
		ID: "exec_tx", PipelineID: "p1", Status: ExecSuccess,
		StartedAt: started, EndedAt: &ended,
	}
	results := []AssertionResult{
		{ID: "ar_tx_1", AssertionID: "a1", ExecutionID: "exec_tx", Status: ResultPass, EvaluatedAt: time.Now().UTC()},
	}

	if err := RecordExecutionWithResults(db, e, results); err != nil {
		t.Fatalf("record: %v", err)
	}

	// Verify both were persisted
	gotExec, _ := db.GetExecution("exec_tx")
	if gotExec == nil {
		t.Fatal("execution not found")
	}

	gotResult, _ := db.GetAssertionResult("ar_tx_1")
	if gotResult == nil {
		t.Fatal("assertion result not found")
	}
}

func TestGetEntityGraph(t *testing.T) {
	db := tempDB(t)

	db.InsertTypeSnapshot(&TypeSnapshot{ID: "t1", Version: "1.0.0", Lang: "go", Algebraic: "product"})
	db.InsertEntity(&Entity{ID: "a", Name: "a", TypeRef: "t1", Status: StatusActive, Source: "test"})
	db.InsertEntity(&Entity{ID: "b", Name: "b", TypeRef: "t1", Status: StatusActive, Source: "test"})
	InsertRelationSafe(db, &Relation{
		ID: "ab", Name: "FLUYE", FromEntity: "a", ToEntity: "b",
		Direction: DirUnidirectional, Status: RelDesigned,
	})

	g, err := GetEntityGraph(db)
	if err != nil {
		t.Fatalf("graph: %v", err)
	}
	if len(g.Entities) != 2 {
		t.Errorf("entities = %d, want 2", len(g.Entities))
	}
	if len(g.Relations) != 1 {
		t.Errorf("relations = %d, want 1", len(g.Relations))
	}
}
