package spider

import (
	"context"
	"os"
	"testing"
	"time"

	"next.orly.dev/pkg/database"
)

func TestSpiderCreation(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create a temporary database for testing
	tempDir, err := os.MkdirTemp("", "spider-test-*")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	db, err := database.New(ctx, cancel, tempDir, "error")
	if err != nil {
		t.Fatalf("Failed to create test database: %v", err)
	}
	defer db.Close()

	// Test spider creation
	spider, err := New(ctx, db, nil, "follows")
	if err != nil {
		t.Fatalf("Failed to create spider: %v", err)
	}

	if spider == nil {
		t.Fatal("Spider is nil")
	}

	// Test that spider is not running initially
	spider.mu.RLock()
	running := spider.running
	spider.mu.RUnlock()

	if running {
		t.Error("Spider should not be running initially")
	}
}

func TestSpiderCallbacks(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create a temporary database for testing
	tempDir, err := os.MkdirTemp("", "spider-test-*")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	db, err := database.New(ctx, cancel, tempDir, "error")
	if err != nil {
		t.Fatalf("Failed to create test database: %v", err)
	}
	defer db.Close()

	spider, err := New(ctx, db, nil, "follows")
	if err != nil {
		t.Fatalf("Failed to create spider: %v", err)
	}

	// Test callback setup
	testRelays := []string{"wss://relay1.example.com", "wss://relay2.example.com"}
	testPubkeys := [][]byte{{1, 2, 3}, {4, 5, 6}}

	spider.SetCallbacks(
		func() []string { return testRelays },
		func() [][]byte { return testPubkeys },
	)

	// Verify callbacks are set
	spider.mu.RLock()
	hasCallbacks := spider.getAdminRelays != nil && spider.getFollowList != nil
	spider.mu.RUnlock()

	if !hasCallbacks {
		t.Error("Callbacks should be set")
	}

	// Test that start fails without callbacks being set first
	spider2, err := New(ctx, db, nil, "follows")
	if err != nil {
		t.Fatalf("Failed to create second spider: %v", err)
	}

	err = spider2.Start()
	if err == nil {
		t.Error("Start should fail when callbacks are not set")
	}
}

func TestSpiderModeValidation(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create a temporary database for testing
	tempDir, err := os.MkdirTemp("", "spider-test-*")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	db, err := database.New(ctx, cancel, tempDir, "error")
	if err != nil {
		t.Fatalf("Failed to create test database: %v", err)
	}
	defer db.Close()

	// Test valid mode
	spider, err := New(ctx, db, nil, "follows")
	if err != nil {
		t.Fatalf("Failed to create spider with valid mode: %v", err)
	}
	if spider == nil {
		t.Fatal("Spider should not be nil for valid mode")
	}

	// Test invalid mode
	_, err = New(ctx, db, nil, "invalid")
	if err == nil {
		t.Error("Should fail with invalid mode")
	}

	// Test none mode (should succeed but be a no-op)
	spider2, err := New(ctx, db, nil, "none")
	if err != nil {
		t.Errorf("Should succeed with 'none' mode: %v", err)
	}
	if spider2 == nil {
		t.Error("Spider should not be nil for 'none' mode")
	}

	// Test that 'none' mode doesn't require callbacks
	err = spider2.Start()
	if err != nil {
		t.Errorf("'none' mode should start without callbacks: %v", err)
	}
}

func TestSpiderBatching(t *testing.T) {
	// Test batch creation logic
	followList := make([][]byte, 50) // 50 pubkeys
	for i := range followList {
		followList[i] = make([]byte, 32)
		for j := range followList[i] {
			followList[i][j] = byte(i)
		}
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	rc := &RelayConnection{
		url: "wss://test.relay.com",
		ctx: ctx,
	}

	batches := rc.createBatches(followList)

	// Should create 3 batches: 20, 20, 10
	expectedBatches := 3
	if len(batches) != expectedBatches {
		t.Errorf("Expected %d batches, got %d", expectedBatches, len(batches))
	}

	// Check batch sizes
	if len(batches[0]) != BatchSize {
		t.Errorf("First batch should have %d pubkeys, got %d", BatchSize, len(batches[0]))
	}
	if len(batches[1]) != BatchSize {
		t.Errorf("Second batch should have %d pubkeys, got %d", BatchSize, len(batches[1]))
	}
	if len(batches[2]) != 10 {
		t.Errorf("Third batch should have 10 pubkeys, got %d", len(batches[2]))
	}
}

func TestSpiderStartStop(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create a temporary database for testing
	tempDir, err := os.MkdirTemp("", "spider-test-*")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	db, err := database.New(ctx, cancel, tempDir, "error")
	if err != nil {
		t.Fatalf("Failed to create test database: %v", err)
	}
	defer db.Close()

	spider, err := New(ctx, db, nil, "follows")
	if err != nil {
		t.Fatalf("Failed to create spider: %v", err)
	}

	// Set up callbacks
	spider.SetCallbacks(
		func() []string { return []string{"wss://test.relay.com"} },
		func() [][]byte { return [][]byte{{1, 2, 3}} },
	)

	// Test start
	err = spider.Start()
	if err != nil {
		t.Fatalf("Failed to start spider: %v", err)
	}

	// Verify spider is running
	spider.mu.RLock()
	running := spider.running
	spider.mu.RUnlock()

	if !running {
		t.Error("Spider should be running after start")
	}

	// Test stop
	spider.Stop()

	// Give it a moment to stop
	time.Sleep(100 * time.Millisecond)

	// Verify spider is stopped
	spider.mu.RLock()
	running = spider.running
	spider.mu.RUnlock()

	if running {
		t.Error("Spider should not be running after stop")
	}
}
