//go:build integration
// +build integration

package neo4j

import (
	"bytes"
	"context"
	"encoding/json"
	"testing"
	"time"

	"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/kind"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"git.mleku.dev/mleku/nostr/encoders/timestamp"
	"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
)

// 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 TestExpiration_SaveEventWithExpiration(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 event with expiration tag (expires in 1 hour)
	futureExpiration := time.Now().Add(1 * time.Hour).Unix()

	ev := event.New()
	ev.Pubkey = signer.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Kind = 1
	ev.Content = []byte("Event with expiration")
	ev.Tags = tag.NewS(tag.NewFromAny("expiration", timestamp.FromUnix(futureExpiration).String()))

	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 the event to verify it was saved
	evs, err := testDB.QueryEvents(ctx, &filter.F{
		Ids: tag.NewFromBytesSlice(ev.ID),
	})
	if err != nil {
		t.Fatalf("Failed to query event: %v", err)
	}

	if len(evs) != 1 {
		t.Fatalf("Expected 1 event, got %d", len(evs))
	}

	t.Logf("✓ Event with expiration tag saved successfully")
}

func TestExpiration_DeleteExpiredEvents(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 an expired event (expired 1 hour ago)
	pastExpiration := time.Now().Add(-1 * time.Hour).Unix()

	expiredEv := event.New()
	expiredEv.Pubkey = signer.Pub()
	expiredEv.CreatedAt = timestamp.Now().V - 7200 // 2 hours ago
	expiredEv.Kind = 1
	expiredEv.Content = []byte("Expired event")
	expiredEv.Tags = tag.NewS(tag.NewFromAny("expiration", timestamp.FromUnix(pastExpiration).String()))

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

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

	// Create a non-expired event (expires in 1 hour)
	futureExpiration := time.Now().Add(1 * time.Hour).Unix()

	validEv := event.New()
	validEv.Pubkey = signer.Pub()
	validEv.CreatedAt = timestamp.Now().V
	validEv.Kind = 1
	validEv.Content = []byte("Valid event")
	validEv.Tags = tag.NewS(tag.NewFromAny("expiration", timestamp.FromUnix(futureExpiration).String()))

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

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

	// Create an event without expiration
	permanentEv := event.New()
	permanentEv.Pubkey = signer.Pub()
	permanentEv.CreatedAt = timestamp.Now().V + 1
	permanentEv.Kind = 1
	permanentEv.Content = []byte("Permanent event (no expiration)")

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

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

	// Verify all 3 events exist
	evs, err := testDB.QueryEvents(ctx, &filter.F{
		Authors: tag.NewFromBytesSlice(signer.Pub()),
	})
	if err != nil {
		t.Fatalf("Failed to query events: %v", err)
	}
	if len(evs) != 3 {
		t.Fatalf("Expected 3 events before deletion, got %d", len(evs))
	}

	// Run DeleteExpired
	testDB.DeleteExpired()

	// Verify only expired event was deleted
	evs, err = testDB.QueryEvents(ctx, &filter.F{
		Authors: tag.NewFromBytesSlice(signer.Pub()),
	})
	if err != nil {
		t.Fatalf("Failed to query events after deletion: %v", err)
	}

	if len(evs) != 2 {
		t.Fatalf("Expected 2 events after deletion (expired removed), got %d", len(evs))
	}

	// Verify the correct events remain
	foundValid := false
	foundPermanent := false
	for _, ev := range evs {
		if hex.Enc(ev.ID[:]) == hex.Enc(validEv.ID[:]) {
			foundValid = true
		}
		if hex.Enc(ev.ID[:]) == hex.Enc(permanentEv.ID[:]) {
			foundPermanent = true
		}
	}

	if !foundValid {
		t.Fatal("Valid event (with future expiration) was incorrectly deleted")
	}
	if !foundPermanent {
		t.Fatal("Permanent event (no expiration) was incorrectly deleted")
	}

	t.Logf("✓ DeleteExpired correctly removed only expired events")
}

func TestExpiration_NoExpirationTag(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 event without expiration tag
	ev := event.New()
	ev.Pubkey = signer.Pub()
	ev.CreatedAt = timestamp.Now().V
	ev.Kind = 1
	ev.Content = []byte("Event without expiration")

	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)
	}

	// Run DeleteExpired - event should not be deleted
	testDB.DeleteExpired()

	// Verify event still exists
	evs, err := testDB.QueryEvents(ctx, &filter.F{
		Ids: tag.NewFromBytesSlice(ev.ID),
	})
	if err != nil {
		t.Fatalf("Failed to query event: %v", err)
	}

	if len(evs) != 1 {
		t.Fatalf("Expected 1 event (no expiration should not be deleted), got %d", len(evs))
	}

	t.Logf("✓ Events without expiration tag are not deleted")
}

func TestExport_AllEvents(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 some 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 for export")
		ev.Tags = tag.NewS(tag.NewFromAny("t", "test"))

		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)
		}
	}

	// Export all events
	var buf bytes.Buffer
	testDB.Export(ctx, &buf)

	// Parse the exported JSONL
	lines := bytes.Split(buf.Bytes(), []byte("\n"))
	validLines := 0
	for _, line := range lines {
		if len(line) == 0 {
			continue
		}
		var ev event.E
		if err := json.Unmarshal(line, &ev); err != nil {
			t.Fatalf("Failed to parse exported event: %v", err)
		}
		validLines++
	}

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

	t.Logf("✓ Export all events returned %d events in JSONL format", validLines)
}

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

	cleanTestDatabase()

	ctx := context.Background()

	// Create two signers
	alice, _ := p8k.New()
	alice.Generate()

	bob, _ := p8k.New()
	bob.Generate()

	baseTs := timestamp.Now().V

	// Create events from Alice
	for i := 0; i < 3; i++ {
		ev := event.New()
		ev.Pubkey = alice.Pub()
		ev.CreatedAt = baseTs + int64(i)
		ev.Kind = 1
		ev.Content = []byte("Alice's event")

		if err := ev.Sign(alice); 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)
		}
	}

	// Create events from Bob
	for i := 0; i < 2; i++ {
		ev := event.New()
		ev.Pubkey = bob.Pub()
		ev.CreatedAt = baseTs + int64(i) + 10
		ev.Kind = 1
		ev.Content = []byte("Bob's event")

		if err := ev.Sign(bob); 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)
		}
	}

	// Export only Alice's events
	var buf bytes.Buffer
	testDB.Export(ctx, &buf, alice.Pub())

	// Parse the exported JSONL
	lines := bytes.Split(buf.Bytes(), []byte("\n"))
	validLines := 0
	alicePubkey := hex.Enc(alice.Pub())
	for _, line := range lines {
		if len(line) == 0 {
			continue
		}
		var ev event.E
		if err := json.Unmarshal(line, &ev); err != nil {
			t.Fatalf("Failed to parse exported event: %v", err)
		}
		if hex.Enc(ev.Pubkey[:]) != alicePubkey {
			t.Fatalf("Exported event has wrong pubkey (expected Alice)")
		}
		validLines++
	}

	if validLines != 3 {
		t.Fatalf("Expected 3 events from Alice, got %d", validLines)
	}

	t.Logf("✓ Export with pubkey filter returned %d events from Alice only", validLines)
}

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

	cleanTestDatabase()

	ctx := context.Background()

	// Export from empty database
	var buf bytes.Buffer
	testDB.Export(ctx, &buf)

	// Should be empty or just whitespace
	content := bytes.TrimSpace(buf.Bytes())
	if len(content) != 0 {
		t.Fatalf("Expected empty export, got: %s", string(content))
	}

	t.Logf("✓ Export from empty database returns empty result")
}

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

	cleanTestDatabase()

	ctx := context.Background()

	signer, _ := p8k.New()
	signer.Generate()

	// Create original events
	originalEvents := make([]*event.E, 3)
	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("Round trip test event")
		ev.Tags = tag.NewS(tag.NewFromAny("t", "roundtrip"))

		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)
		}
		originalEvents[i] = ev
	}

	// Export events
	var buf bytes.Buffer
	testDB.Export(ctx, &buf)

	// Wipe database
	if err := testDB.Wipe(); err != nil {
		t.Fatalf("Failed to wipe database: %v", err)
	}

	// Verify database is empty
	evs, err := testDB.QueryEvents(ctx, &filter.F{
		Kinds: kind.NewS(kind.New(1)),
	})
	if err != nil {
		t.Fatalf("Failed to query events: %v", err)
	}
	if len(evs) != 0 {
		t.Fatalf("Expected 0 events after wipe, got %d", len(evs))
	}

	// Import events
	testDB.Import(bytes.NewReader(buf.Bytes()))

	// Verify events were restored
	evs, err = testDB.QueryEvents(ctx, &filter.F{
		Authors: tag.NewFromBytesSlice(signer.Pub()),
	})
	if err != nil {
		t.Fatalf("Failed to query imported events: %v", err)
	}

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

	// Verify event IDs match
	importedIDs := make(map[string]bool)
	for _, ev := range evs {
		importedIDs[hex.Enc(ev.ID[:])] = true
	}

	for _, orig := range originalEvents {
		if !importedIDs[hex.Enc(orig.ID[:])] {
			t.Fatalf("Original event %s not found after import", hex.Enc(orig.ID[:]))
		}
	}

	t.Logf("✓ Export/Import round trip preserved %d events correctly", len(evs))
}
