//go:build integration
// +build integration

package neo4j

import (
	"context"
	"testing"

	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/filter"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"git.mleku.dev/mleku/nostr/encoders/timestamp"
	"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
	"next.orly.dev/pkg/database/indexes/types"
)

// All tests in this file use the shared testDB instance from testmain_test.go
// to avoid Neo4j authentication rate limiting from too many connections.

func TestFetchEventBySerial(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	cleanTestDatabase()

	ctx := context.Background()

	signer, err := p8k.New()
	if err != nil {
		t.Fatalf("Failed to create signer: %v", err)
	}
	if err := signer.Generate(); err != nil {
		t.Fatalf("Failed to generate keypair: %v", err)
	}

	// Create and save a test event
	ev := event.New()
	ev.Pubkey = signer.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Kind = 1
	ev.Content = []byte("Test event for fetch by serial")

	if err := ev.Sign(signer); err != nil {
		t.Fatalf("Failed to sign event: %v", err)
	}

	if _, err := testDB.SaveEvent(ctx, ev); err != nil {
		t.Fatalf("Failed to save event: %v", err)
	}

	// Get the serial for this event
	serial, err := testDB.GetSerialById(ev.ID[:])
	if err != nil {
		t.Fatalf("Failed to get serial by ID: %v", err)
	}

	// Fetch event by serial
	fetchedEvent, err := testDB.FetchEventBySerial(serial)
	if err != nil {
		t.Fatalf("Failed to fetch event by serial: %v", err)
	}

	if fetchedEvent == nil {
		t.Fatal("Expected fetched event to be non-nil")
	}

	// Verify event properties
	if hex.Enc(fetchedEvent.ID[:]) != hex.Enc(ev.ID[:]) {
		t.Fatalf("Event ID mismatch: got %s, expected %s",
			hex.Enc(fetchedEvent.ID[:]), hex.Enc(ev.ID[:]))
	}

	if fetchedEvent.Kind != ev.Kind {
		t.Fatalf("Kind mismatch: got %d, expected %d", fetchedEvent.Kind, ev.Kind)
	}

	if hex.Enc(fetchedEvent.Pubkey[:]) != hex.Enc(ev.Pubkey[:]) {
		t.Fatalf("Pubkey mismatch")
	}

	if fetchedEvent.CreatedAt != ev.CreatedAt {
		t.Fatalf("CreatedAt mismatch: got %d, expected %d",
			fetchedEvent.CreatedAt, ev.CreatedAt)
	}

	t.Logf("✓ FetchEventBySerial returned correct event")
}

func TestFetchEventBySerial_NonExistent(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	// Try to fetch with non-existent serial
	nonExistentSerial := &types.Uint40{}
	nonExistentSerial.Set(0xFFFFFFFFFF) // Max value

	_, err := testDB.FetchEventBySerial(nonExistentSerial)
	if err == nil {
		t.Fatal("Expected error for non-existent serial")
	}

	t.Logf("✓ FetchEventBySerial correctly returned error for non-existent serial")
}

func TestFetchEventsBySerials(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	cleanTestDatabase()

	ctx := context.Background()

	signer, err := p8k.New()
	if err != nil {
		t.Fatalf("Failed to create signer: %v", err)
	}
	if err := signer.Generate(); err != nil {
		t.Fatalf("Failed to generate keypair: %v", err)
	}

	// Create and save multiple events
	var serials []*types.Uint40
	eventIDs := make(map[uint64]string)

	for i := 0; i < 5; i++ {
		ev := event.New()
		ev.Pubkey = signer.Pub()
		ev.CreatedAt = timestamp.Now().V + int64(i)
		ev.Kind = 1
		ev.Content = []byte("Test event")

		if err := ev.Sign(signer); err != nil {
			t.Fatalf("Failed to sign event: %v", err)
		}

		if _, err := testDB.SaveEvent(ctx, ev); err != nil {
			t.Fatalf("Failed to save event: %v", err)
		}

		serial, err := testDB.GetSerialById(ev.ID[:])
		if err != nil {
			t.Fatalf("Failed to get serial: %v", err)
		}

		serials = append(serials, serial)
		eventIDs[serial.Get()] = hex.Enc(ev.ID[:])
	}

	// Fetch all events by serials
	events, err := testDB.FetchEventsBySerials(serials)
	if err != nil {
		t.Fatalf("Failed to fetch events by serials: %v", err)
	}

	if len(events) != 5 {
		t.Fatalf("Expected 5 events, got %d", len(events))
	}

	// Verify each event
	for serial, expectedID := range eventIDs {
		ev, exists := events[serial]
		if !exists {
			t.Fatalf("Event with serial %d not found", serial)
		}
		if hex.Enc(ev.ID[:]) != expectedID {
			t.Fatalf("Event ID mismatch for serial %d", serial)
		}
	}

	t.Logf("✓ FetchEventsBySerials returned %d correct events", len(events))
}

func TestGetSerialById(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	cleanTestDatabase()

	ctx := context.Background()

	signer, err := p8k.New()
	if err != nil {
		t.Fatalf("Failed to create signer: %v", err)
	}
	if err := signer.Generate(); err != nil {
		t.Fatalf("Failed to generate keypair: %v", err)
	}

	// Create and save event
	ev := event.New()
	ev.Pubkey = signer.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Kind = 1
	ev.Content = []byte("Test event")

	if err := ev.Sign(signer); err != nil {
		t.Fatalf("Failed to sign event: %v", err)
	}

	if _, err := testDB.SaveEvent(ctx, ev); err != nil {
		t.Fatalf("Failed to save event: %v", err)
	}

	// Get serial by ID
	serial, err := testDB.GetSerialById(ev.ID[:])
	if err != nil {
		t.Fatalf("Failed to get serial by ID: %v", err)
	}

	if serial == nil {
		t.Fatal("Expected serial to be non-nil")
	}

	if serial.Get() == 0 {
		t.Fatal("Expected non-zero serial")
	}

	t.Logf("✓ GetSerialById returned serial: %d", serial.Get())
}

func TestGetSerialById_NonExistent(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	// Try to get serial for non-existent event
	fakeID, _ := hex.Dec("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")

	_, err := testDB.GetSerialById(fakeID)
	if err == nil {
		t.Fatal("Expected error for non-existent event ID")
	}

	t.Logf("✓ GetSerialById correctly returned error for non-existent ID")
}

func TestGetSerialsByIds(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	cleanTestDatabase()

	ctx := context.Background()

	signer, err := p8k.New()
	if err != nil {
		t.Fatalf("Failed to create signer: %v", err)
	}
	if err := signer.Generate(); err != nil {
		t.Fatalf("Failed to generate keypair: %v", err)
	}

	// Create and save multiple events
	ids := tag.New()
	for i := 0; i < 3; i++ {
		ev := event.New()
		ev.Pubkey = signer.Pub()
		ev.CreatedAt = timestamp.Now().V + int64(i)
		ev.Kind = 1
		ev.Content = []byte("Test event")

		if err := ev.Sign(signer); err != nil {
			t.Fatalf("Failed to sign event: %v", err)
		}

		if _, err := testDB.SaveEvent(ctx, ev); err != nil {
			t.Fatalf("Failed to save event: %v", err)
		}

		// Append ID to the tag's T slice
		ids.T = append(ids.T, []byte(hex.Enc(ev.ID[:])))
	}

	// Get serials by IDs
	serials, err := testDB.GetSerialsByIds(ids)
	if err != nil {
		t.Fatalf("Failed to get serials by IDs: %v", err)
	}

	if len(serials) != 3 {
		t.Fatalf("Expected 3 serials, got %d", len(serials))
	}

	t.Logf("✓ GetSerialsByIds returned %d serials", len(serials))
}

func TestGetFullIdPubkeyBySerial(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	cleanTestDatabase()

	ctx := context.Background()

	signer, err := p8k.New()
	if err != nil {
		t.Fatalf("Failed to create signer: %v", err)
	}
	if err := signer.Generate(); err != nil {
		t.Fatalf("Failed to generate keypair: %v", err)
	}

	// Create and save event
	ev := event.New()
	ev.Pubkey = signer.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Kind = 1
	ev.Content = []byte("Test event")

	if err := ev.Sign(signer); err != nil {
		t.Fatalf("Failed to sign event: %v", err)
	}

	if _, err := testDB.SaveEvent(ctx, ev); err != nil {
		t.Fatalf("Failed to save event: %v", err)
	}

	// Get serial
	serial, err := testDB.GetSerialById(ev.ID[:])
	if err != nil {
		t.Fatalf("Failed to get serial: %v", err)
	}

	// Get full ID and pubkey
	idPkTs, err := testDB.GetFullIdPubkeyBySerial(serial)
	if err != nil {
		t.Fatalf("Failed to get full ID and pubkey: %v", err)
	}

	if idPkTs == nil {
		t.Fatal("Expected non-nil result")
	}

	if idPkTs.IDHex() != hex.Enc(ev.ID[:]) {
		t.Fatalf("ID mismatch")
	}

	if idPkTs.PubHex() != hex.Enc(ev.Pubkey[:]) {
		t.Fatalf("Pubkey mismatch")
	}

	if idPkTs.Ts != ev.CreatedAt {
		t.Fatalf("Timestamp mismatch")
	}

	t.Logf("✓ GetFullIdPubkeyBySerial returned correct data")
}

func TestQueryForSerials(t *testing.T) {
	if testDB == nil {
		t.Skip("Neo4j not available")
	}

	cleanTestDatabase()

	ctx := context.Background()

	signer, err := p8k.New()
	if err != nil {
		t.Fatalf("Failed to create signer: %v", err)
	}
	if err := signer.Generate(); err != nil {
		t.Fatalf("Failed to generate keypair: %v", err)
	}

	// Create and save events
	for i := 0; i < 5; i++ {
		ev := event.New()
		ev.Pubkey = signer.Pub()
		ev.CreatedAt = timestamp.Now().V + int64(i)
		ev.Kind = 1
		ev.Content = []byte("Test event")

		if err := ev.Sign(signer); err != nil {
			t.Fatalf("Failed to sign event: %v", err)
		}

		if _, err := testDB.SaveEvent(ctx, ev); err != nil {
			t.Fatalf("Failed to save event: %v", err)
		}
	}

	// Query for serials
	serials, err := testDB.QueryForSerials(ctx, &filter.F{
		Authors: tag.NewFromBytesSlice(signer.Pub()),
	})
	if err != nil {
		t.Fatalf("Failed to query for serials: %v", err)
	}

	if len(serials) != 5 {
		t.Fatalf("Expected 5 serials, got %d", len(serials))
	}

	t.Logf("✓ QueryForSerials returned %d serials", len(serials))
}
