package blockassembly

import (
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// TestUnminedTransactionLoadingFlag tests that the unmined transaction loading flag
// correctly prevents service operations during loading
func TestUnminedTransactionLoadingFlag(t *testing.T) {
	b := &BlockAssembler{
		unminedTransactionsLoading: atomic.Bool{},
		currentRunningState:        atomic.Value{},
	}

	// Initially, flag should be false
	assert.False(t, b.unminedTransactionsLoading.Load(), "Flag should initially be false")

	// Set the flag
	b.unminedTransactionsLoading.Store(true)
	assert.True(t, b.unminedTransactionsLoading.Load(), "Flag should be true after setting")

	// Clear the flag
	b.unminedTransactionsLoading.Store(false)
	assert.False(t, b.unminedTransactionsLoading.Load(), "Flag should be false after clearing")
}

// TestUnminedTransactionLoadingSetsFlag tests that loadUnminedTransactions
// correctly sets and clears the loading flag
func TestUnminedTransactionLoadingSetsFlag(t *testing.T) {
	// Create a minimal BlockAssembler for testing
	b := &BlockAssembler{
		unminedTransactionsLoading: atomic.Bool{},
	}

	// Track when loading starts and ends
	loadingStarted := make(chan struct{})
	loadingEnded := make(chan struct{})

	// Flag should be false initially
	assert.False(t, b.unminedTransactionsLoading.Load(),
		"Flag should be false before loading")

	// Simulate loadUnminedTransactions behavior
	go func() {
		// Simulate what the real function does
		b.unminedTransactionsLoading.Store(true)
		defer b.unminedTransactionsLoading.Store(false)

		// The actual implementation should set the flag
		assert.True(t, b.unminedTransactionsLoading.Load(),
			"Flag should be set at start of loadUnminedTransactions")
		close(loadingStarted)

		// Simulate some work
		time.Sleep(50 * time.Millisecond)

		// Before returning, flag should still be set
		assert.True(t, b.unminedTransactionsLoading.Load(),
			"Flag should still be set before return")

		close(loadingEnded)
	}()

	// Wait for loading to start
	<-loadingStarted

	// Wait for loading to end
	<-loadingEnded

	// Give defer time to execute
	time.Sleep(10 * time.Millisecond)

	// Flag should be cleared after function returns
	assert.False(t, b.unminedTransactionsLoading.Load(),
		"Flag should be cleared after loadUnminedTransactions returns")
}

// TestServiceNotReadyDuringLoading tests that gRPC methods return
// "service not ready" errors when unmined transactions are being loaded
func TestServiceNotReadyDuringLoading(t *testing.T) {
	// This test verifies the logic that's implemented in Server.go
	// The actual gRPC methods check:
	// if ba.blockAssembler.unminedTransactionsLoading.Load() {
	//     return nil, errors.NewServiceError("service not ready - unmined transactions are still being loaded")
	// }

	// Create a minimal BlockAssembler for testing the logic
	b := &BlockAssembler{
		unminedTransactionsLoading: atomic.Bool{},
		currentRunningState:        atomic.Value{},
	}

	// Initialize the state
	b.currentRunningState.Store(StateRunning)

	// Simulate loading in progress
	b.unminedTransactionsLoading.Store(true)

	// When loading is true, gRPC methods should return service not ready
	assert.True(t, b.unminedTransactionsLoading.Load(),
		"Loading flag indicates service is not ready")

	// Clear loading flag
	b.unminedTransactionsLoading.Store(false)

	// Now service should be ready
	assert.False(t, b.unminedTransactionsLoading.Load(),
		"With loading flag cleared, service is ready")
}

// TestNoHotLoopInResetHandler tests that there's no hot loop when
// reset is triggered during loading (since we removed the re-queuing logic)
func TestNoHotLoopInResetHandler(t *testing.T) {
	// Create a minimal BlockAssembler with reset channel
	b := &BlockAssembler{
		unminedTransactionsLoading: atomic.Bool{},
		currentRunningState:        atomic.Value{},
		resetCh:                    make(chan chan error, 2),
	}

	// Initialize the state
	b.currentRunningState.Store(StateRunning)

	// Simulate loading in progress
	b.unminedTransactionsLoading.Store(true)

	// Send a reset signal
	b.resetCh <- nil

	// With the new implementation, the reset handler will:
	// 1. Receive the reset signal
	// 2. Proceed with reset immediately (no deferral)
	// 3. The gRPC ResetBlockAssembly method would return "service not ready" if called

	// Verify the channel has the reset signal
	select {
	case <-b.resetCh:
		// Good, reset was consumed (would be processed)
	default:
		t.Error("Reset signal should have been in the channel")
	}

	// Verify channel is now empty (no re-queuing)
	select {
	case <-b.resetCh:
		t.Error("Channel should be empty - no re-queuing should occur")
	default:
		// Good, channel is empty
	}
}
