package sql

import (
	"context"
	"net/url"
	"testing"

	"github.com/bsv-blockchain/teranode/ulogger"
	"github.com/bsv-blockchain/teranode/util/test"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// Test successful retrieval of headers within height range
func TestGetBlockHeadersByHeight_Success(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store multiple blocks to create a chain
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block2, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block3, "test_peer")
	require.NoError(t, err)

	// Test getting headers from height 1 to 2
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 1, 2)

	// Assertions
	require.NoError(t, err)
	assert.NotNil(t, headers)
	assert.NotNil(t, metas)
	assert.Equal(t, len(headers), len(metas))
	assert.Greater(t, len(headers), 0)

	// Verify headers are in ascending height order
	for i := 1; i < len(metas); i++ {
		assert.LessOrEqual(t, metas[i-1].Height, metas[i].Height)
	}

	// Verify all headers are within the requested range
	for _, meta := range metas {
		assert.GreaterOrEqual(t, meta.Height, uint32(1))
		assert.LessOrEqual(t, meta.Height, uint32(2))
	}

	// Verify header structure integrity
	for i, header := range headers {
		assert.NotNil(t, header.HashPrevBlock)
		assert.NotNil(t, header.HashMerkleRoot)
		assert.Greater(t, header.Version, uint32(0))

		meta := metas[i]
		assert.Greater(t, meta.Height, uint32(0))
		assert.Greater(t, meta.ID, uint32(0))
		assert.NotEmpty(t, meta.PeerID)
	}
}

// Test empty result when no blocks exist in range
func TestGetBlockHeadersByHeight_EmptyResult(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store one block at height 1
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	// Request headers from height 100-200 (should be empty)
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 100, 200)

	// Should return empty slices with no error
	require.NoError(t, err)
	assert.Empty(t, headers)
	assert.Empty(t, metas)
}

// Test with same start and end height
func TestGetBlockHeadersByHeight_SameHeight(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store blocks
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block2, "test_peer")
	require.NoError(t, err)

	// Request headers for exactly height 2
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 2, 2)

	require.NoError(t, err)
	assert.NotEmpty(t, headers)
	assert.NotEmpty(t, metas)
	assert.Equal(t, len(headers), len(metas))

	// All results should be at height 2
	for _, meta := range metas {
		assert.Equal(t, uint32(2), meta.Height)
	}
}

// Test with startHeight > endHeight (reverse range)
func TestGetBlockHeadersByHeight_ReverseRange(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store blocks
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block2, "test_peer")
	require.NoError(t, err)

	// Request with startHeight > endHeight
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 2, 1)

	// Should return empty results (no blocks match the impossible range)
	require.NoError(t, err)
	assert.Empty(t, headers)
	assert.Empty(t, metas)
}

// Test context cancellation
func TestGetBlockHeadersByHeight_ContextCancellation(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	// Create cancelled context
	ctx, cancel := context.WithCancel(context.Background())
	cancel() // Cancel immediately

	// Should return error due to cancelled context
	_, _, err = s.GetBlockHeadersByHeight(ctx, 1, 10)

	assert.Error(t, err)
}

// Test with zero height range (edge case)
func TestGetBlockHeadersByHeight_ZeroHeight(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Request genesis block (height 0)
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 0, 0)

	require.NoError(t, err)
	assert.NotEmpty(t, headers) // Genesis block should exist
	assert.NotEmpty(t, metas)
	assert.Equal(t, len(headers), len(metas))

	// Verify genesis block properties
	for _, meta := range metas {
		assert.Equal(t, uint32(0), meta.Height)
	}
}

// Test with large height range
func TestGetBlockHeadersByHeight_LargeRange(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store some blocks
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block2, "test_peer")
	require.NoError(t, err)

	// Request very large range
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 0, 1000000)

	require.NoError(t, err)
	assert.NotNil(t, headers)
	assert.NotNil(t, metas)
	assert.Equal(t, len(headers), len(metas))

	// Should only return blocks that exist (genesis + stored blocks)
	assert.Greater(t, len(headers), 0)
	assert.LessOrEqual(t, len(headers), 10) // reasonable upper bound
}

// Test filtering of invalid blocks
func TestGetBlockHeadersByHeight_InvalidBlocks(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store blocks first
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block2, "test_peer")
	require.NoError(t, err)

	// Get count before invalidation
	headersBefore, _, err := s.GetBlockHeadersByHeight(ctx, 0, 10)
	require.NoError(t, err)
	countBefore := len(headersBefore)

	// Invalidate block2
	_, err = s.InvalidateBlock(ctx, block2.Header.Hash())
	require.NoError(t, err)

	// Get headers again - invalid blocks should be filtered out
	headersAfter, metasAfter, err := s.GetBlockHeadersByHeight(ctx, 0, 10)
	require.NoError(t, err)

	// Should have fewer headers now (invalid block filtered out)
	assert.LessOrEqual(t, len(headersAfter), countBefore)
	assert.Equal(t, len(headersAfter), len(metasAfter))

	// Verify all returned blocks are valid (no invalid flag set)
	for _, header := range headersAfter {
		// All headers should be from valid blocks since invalid ones are filtered
		assert.NotNil(t, header)
	}
}

// Test with maximum uint32 values
func TestGetBlockHeadersByHeight_MaxValues(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Request with very high height values (should be empty)
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 4294967290, 4294967295) // near uint32 max

	require.NoError(t, err)
	assert.Empty(t, headers) // No blocks at such high heights
	assert.Empty(t, metas)
}

// Test data consistency between headers and metas
func TestGetBlockHeadersByHeight_DataConsistency(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store blocks
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block2, "test_peer")
	require.NoError(t, err)

	// Get headers in range
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 0, 5)

	require.NoError(t, err)
	assert.Equal(t, len(headers), len(metas))

	// Just verify that we get valid non-nil results
	for i, header := range headers {
		meta := metas[i]

		// Basic structure validation
		assert.NotNil(t, header, "Header should not be nil")
		assert.NotNil(t, meta, "Meta should not be nil")

		// Verify header hash fields are set
		assert.NotNil(t, header.HashPrevBlock, "HashPrevBlock should be set")
		assert.NotNil(t, header.HashMerkleRoot, "HashMerkleRoot should be set")
	}
}

// Test max helper function with different types and scenarios
func TestMax_Integer(t *testing.T) {
	// Test with integers
	assert.Equal(t, 5, max(3, 5))
	assert.Equal(t, 10, max(10, 7))
	assert.Equal(t, 42, max(42, 42)) // Equal values
	assert.Equal(t, 0, max(-5, 0))   // Negative and positive
}

func TestMax_Float(t *testing.T) {
	// Test with floats
	assert.Equal(t, 3.14, max(2.71, 3.14))
	assert.Equal(t, 1.0, max(1.0, 0.5))
	assert.Equal(t, 2.5, max(2.5, 2.5)) // Equal values
}

func TestMax_String(t *testing.T) {
	// Test with strings (lexicographical ordering)
	assert.Equal(t, "zebra", max("apple", "zebra"))
	assert.Equal(t, "hello", max("hello", "goodbye"))
	assert.Equal(t, "test", max("test", "test")) // Equal values
}

func TestMax_Uint32(t *testing.T) {
	// Test with uint32 (relevant for height values)
	assert.Equal(t, uint32(100), max(uint32(50), uint32(100)))
	assert.Equal(t, uint32(1000), max(uint32(1000), uint32(999)))
	assert.Equal(t, uint32(0), max(uint32(0), uint32(0))) // Both zero
}

// Test edge case with minimum capacity calculation
func TestGetBlockHeadersByHeight_CapacityCalculation(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// This tests the capacity calculation: max(1, endHeight-startHeight+1)
	// When startHeight > endHeight, should still work without panic
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 10, 5)

	require.NoError(t, err)
	assert.Empty(t, headers)
	assert.Empty(t, metas)
}

// Test ordering verification
func TestGetBlockHeadersByHeight_Ordering(t *testing.T) {
	storeURL, err := url.Parse("sqlitememory:///")
	require.NoError(t, err)
	tSettings := test.CreateBaseTestSettings(t)

	s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
	require.NoError(t, err)

	ctx := context.Background()

	// Store multiple blocks
	_, _, err = s.StoreBlock(ctx, block1, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block2, "test_peer")
	require.NoError(t, err)

	_, _, err = s.StoreBlock(ctx, block3, "test_peer")
	require.NoError(t, err)

	// Get all blocks
	headers, metas, err := s.GetBlockHeadersByHeight(ctx, 0, 10)

	require.NoError(t, err)
	assert.Greater(t, len(headers), 1) // Should have multiple blocks

	// Verify strict ascending order
	for i := 1; i < len(metas); i++ {
		assert.LessOrEqual(t, metas[i-1].Height, metas[i].Height, "Headers should be ordered by height (ASC)")
	}
}
