package db_test

import (
	"context"
	"testing"
	"time"
)

// The events table backs cold-tier reconnect replay: when a client's last_seq
// falls out of the in-memory ring, the hub refills from here. GetMaxEventSeq
// (which seeds the hub's counter at startup) and PruneEventsOlderThan (the
// retention job) had no coverage, and neither did the channel filter that
// keeps a replay from leaking events for channels the client cannot see.

func TestPersistEvent_AndGetEventsSince(t *testing.T) {
	database := newMigratedTestDB(t)
	ctx := context.Background()

	for seq := int64(1); seq <= 3; seq++ {
		if err := database.PersistEvent(ctx, seq, "chat_message", 10, []byte(`{"n":1}`)); err != nil {
			t.Fatalf("PersistEvent(%d): %v", seq, err)
		}
	}

	all, err := database.GetEventsSince(ctx, 0, 100)
	if err != nil {
		t.Fatalf("GetEventsSince: %v", err)
	}
	if len(all) != 3 {
		t.Fatalf("GetEventsSince(0) returned %d events, want 3", len(all))
	}
	for i, e := range all {
		if e.Seq != int64(i+1) {
			t.Errorf("event[%d].Seq = %d, want %d — replay depends on ascending order", i, e.Seq, i+1)
		}
		if e.EventType != "chat_message" {
			t.Errorf("event[%d].EventType = %q", i, e.EventType)
		}
		if e.CreatedAt.IsZero() {
			t.Errorf("event[%d].CreatedAt is zero; parseSQLiteTime failed to parse the row", i)
		}
	}

	// afterSeq is exclusive.
	tail, err := database.GetEventsSince(ctx, 2, 100)
	if err != nil {
		t.Fatalf("GetEventsSince(2): %v", err)
	}
	if len(tail) != 1 || tail[0].Seq != 3 {
		t.Errorf("GetEventsSince(2) = %+v, want only seq 3", tail)
	}
}

func TestGetEventsSince_RespectsLimit(t *testing.T) {
	database := newMigratedTestDB(t)
	ctx := context.Background()

	for seq := int64(1); seq <= 10; seq++ {
		if err := database.PersistEvent(ctx, seq, "e", 0, []byte(`{}`)); err != nil {
			t.Fatalf("PersistEvent: %v", err)
		}
	}

	got, err := database.GetEventsSince(ctx, 0, 4)
	if err != nil {
		t.Fatalf("GetEventsSince: %v", err)
	}
	if len(got) != 4 {
		t.Fatalf("returned %d events, want the 4-row limit", len(got))
	}
	if got[0].Seq != 1 || got[3].Seq != 4 {
		t.Errorf("limited window = seq %d..%d, want 1..4", got[0].Seq, got[3].Seq)
	}
}

func TestGetEventsSinceForChannels(t *testing.T) {
	database := newMigratedTestDB(t)
	ctx := context.Background()

	// seq 1: global; 2: channel 10; 3: channel 20; 4: channel 30.
	events := []struct {
		seq       int64
		channelID int64
	}{{1, 0}, {2, 10}, {3, 20}, {4, 30}}
	for _, e := range events {
		if err := database.PersistEvent(ctx, e.seq, "e", e.channelID, []byte(`{}`)); err != nil {
			t.Fatalf("PersistEvent: %v", err)
		}
	}

	t.Run("no channels returns globals only", func(t *testing.T) {
		got, err := database.GetEventsSinceForChannels(ctx, 0, nil, 100)
		if err != nil {
			t.Fatalf("GetEventsSinceForChannels: %v", err)
		}
		if len(got) != 1 || got[0].Seq != 1 {
			t.Errorf("got %+v, want only the global event", got)
		}
	})

	t.Run("visible channels plus globals", func(t *testing.T) {
		got, err := database.GetEventsSinceForChannels(ctx, 0, []int64{10, 20}, 100)
		if err != nil {
			t.Fatalf("GetEventsSinceForChannels: %v", err)
		}
		if len(got) != 3 {
			t.Fatalf("got %d events, want 3 (global + channels 10 and 20): %+v", len(got), got)
		}
		// Channel 30 is not visible to this client and must never appear.
		for _, e := range got {
			if e.ChannelID == 30 {
				t.Errorf("replay leaked an event for channel 30: %+v", e)
			}
		}
	})

	t.Run("afterSeq is applied with the channel filter", func(t *testing.T) {
		got, err := database.GetEventsSinceForChannels(ctx, 2, []int64{10, 20}, 100)
		if err != nil {
			t.Fatalf("GetEventsSinceForChannels: %v", err)
		}
		if len(got) != 1 || got[0].Seq != 3 {
			t.Errorf("got %+v, want only seq 3", got)
		}
	})

	t.Run("limit is applied", func(t *testing.T) {
		got, err := database.GetEventsSinceForChannels(ctx, 0, []int64{10, 20}, 2)
		if err != nil {
			t.Fatalf("GetEventsSinceForChannels: %v", err)
		}
		if len(got) != 2 {
			t.Errorf("got %d events, want the 2-row limit", len(got))
		}
	})
}

func TestGetMaxEventSeq(t *testing.T) {
	database := newMigratedTestDB(t)
	ctx := context.Background()

	// On an empty table MAX(seq) is NULL — the hub seeds its counter from this
	// at startup, so it has to come back as 0 rather than an error.
	got, err := database.GetMaxEventSeq(ctx)
	if err != nil {
		t.Fatalf("GetMaxEventSeq on empty: %v", err)
	}
	if got != 0 {
		t.Errorf("GetMaxEventSeq on an empty table = %d, want 0", got)
	}

	for _, seq := range []int64{1, 7, 4} {
		if err := database.PersistEvent(ctx, seq, "e", 0, []byte(`{}`)); err != nil {
			t.Fatalf("PersistEvent(%d): %v", seq, err)
		}
	}

	got, err = database.GetMaxEventSeq(ctx)
	if err != nil {
		t.Fatalf("GetMaxEventSeq: %v", err)
	}
	if got != 7 {
		t.Errorf("GetMaxEventSeq = %d, want 7 (the highest, not the last inserted)", got)
	}
}

func TestPruneEventsOlderThan(t *testing.T) {
	database := newMigratedTestDB(t)
	ctx := context.Background()

	// created_at defaults to CURRENT_TIMESTAMP, so backdate the old rows
	// explicitly in the same "2006-01-02 15:04:05" format the prune compares.
	old := time.Now().UTC().Add(-48 * time.Hour).Format("2006-01-02 15:04:05")
	for _, seq := range []int64{1, 2} {
		if err := database.PersistEvent(ctx, seq, "e", 0, []byte(`{}`)); err != nil {
			t.Fatalf("PersistEvent: %v", err)
		}
		if _, err := database.ExecContext(ctx,
			`UPDATE events SET created_at = ? WHERE seq = ?`, old, seq); err != nil {
			t.Fatalf("backdate seq %d: %v", seq, err)
		}
	}
	if err := database.PersistEvent(ctx, 3, "e", 0, []byte(`{}`)); err != nil {
		t.Fatalf("PersistEvent: %v", err)
	}

	deleted, err := database.PruneEventsOlderThan(ctx, time.Now().UTC().Add(-24*time.Hour))
	if err != nil {
		t.Fatalf("PruneEventsOlderThan: %v", err)
	}
	if deleted != 2 {
		t.Errorf("deleted = %d, want 2", deleted)
	}

	remaining, err := database.GetEventsSince(ctx, 0, 100)
	if err != nil {
		t.Fatalf("GetEventsSince: %v", err)
	}
	if len(remaining) != 1 || remaining[0].Seq != 3 {
		t.Errorf("remaining = %+v, want only the recent event", remaining)
	}
}

func TestPruneEventsOlderThan_NothingToPrune(t *testing.T) {
	database := newMigratedTestDB(t)
	ctx := context.Background()

	if err := database.PersistEvent(ctx, 1, "e", 0, []byte(`{}`)); err != nil {
		t.Fatalf("PersistEvent: %v", err)
	}

	deleted, err := database.PruneEventsOlderThan(ctx, time.Now().UTC().Add(-24*time.Hour))
	if err != nil {
		t.Fatalf("PruneEventsOlderThan: %v", err)
	}
	if deleted != 0 {
		t.Errorf("deleted = %d, want 0 — a fresh event must survive the retention window", deleted)
	}
}

func TestPersistEvent_DuplicateSeqRejected(t *testing.T) {
	database := newMigratedTestDB(t)
	ctx := context.Background()

	if err := database.PersistEvent(ctx, 1, "e", 0, []byte(`{}`)); err != nil {
		t.Fatalf("PersistEvent: %v", err)
	}
	// seq is the PRIMARY KEY; a duplicate would corrupt replay ordering, so it
	// must surface as an error rather than silently overwrite.
	if err := database.PersistEvent(ctx, 1, "e", 0, []byte(`{}`)); err == nil {
		t.Error("PersistEvent accepted a duplicate seq")
	}
}
