package listener

import (
	"context"
	"fmt"
	"math/big"
	"time"

	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
)

// BlockListener listens for new blocks on the blockchain
type BlockListener struct {
	client       *ethclient.Client
	wsClient     *ethclient.Client
	chainID      int64
	useWebSocket bool
	queue        chan *types.Block
	ctx          context.Context
	cancel       context.CancelFunc
}

// NewBlockListener creates a new block listener
func NewBlockListener(rpcURL, wsURL string, chainID int64) (*BlockListener, error) {
	ctx, cancel := context.WithCancel(context.Background())

	// Connect to RPC
	client, err := ethclient.Dial(rpcURL)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to connect to RPC: %w", err)
	}

	// Try to connect to WebSocket (optional)
	var wsClient *ethclient.Client
	useWebSocket := false
	if wsURL != "" {
		wsClient, err = ethclient.Dial(wsURL)
		if err == nil {
			useWebSocket = true
		}
	}

	return &BlockListener{
		client:       client,
		wsClient:     wsClient,
		chainID:      chainID,
		useWebSocket: useWebSocket,
		queue:        make(chan *types.Block, 100),
		ctx:          ctx,
		cancel:       cancel,
	}, nil
}

// Start starts listening for new blocks
func (bl *BlockListener) Start() error {
	if bl.useWebSocket {
		return bl.startWebSocketListener()
	}
	return bl.startPollingListener()
}

// startWebSocketListener listens via WebSocket subscription
func (bl *BlockListener) startWebSocketListener() error {
	headers := make(chan *types.Header)
	sub, err := bl.wsClient.SubscribeNewHead(bl.ctx, headers)
	if err != nil {
		return fmt.Errorf("failed to subscribe to new heads: %w", err)
	}

	go func() {
		defer sub.Unsubscribe()
		for {
			select {
			case err := <-sub.Err():
				if err != nil {
					// Fallback to polling on WebSocket error
					bl.useWebSocket = false
					go bl.startPollingListener()
					return
				}
			case header := <-headers:
				bl.fetchAndQueueBlock(header.Number.Int64())
			case <-bl.ctx.Done():
				return
			}
		}
	}()

	return nil
}

// startPollingListener polls for new blocks
func (bl *BlockListener) startPollingListener() error {
	var lastBlock int64 = -1

	ticker := time.NewTicker(2 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			blockNumber, err := bl.client.BlockNumber(bl.ctx)
			if err != nil {
				continue
			}

			currentBlock := int64(blockNumber)
			if lastBlock == -1 {
				lastBlock = currentBlock
				continue
			}

			// Process all blocks from lastBlock+1 to currentBlock
			for i := lastBlock + 1; i <= currentBlock; i++ {
				bl.fetchAndQueueBlock(i)
			}

			lastBlock = currentBlock

		case <-bl.ctx.Done():
			return nil
		}
	}
}

// fetchAndQueueBlock fetches a block and queues it for processing
func (bl *BlockListener) fetchAndQueueBlock(blockNumber int64) {
	block, err := bl.client.BlockByNumber(bl.ctx, big.NewInt(blockNumber))
	if err != nil {
		return
	}

	select {
	case bl.queue <- block:
	case <-bl.ctx.Done():
		return
	}
}

// GetBlockChannel returns the channel for receiving blocks
func (bl *BlockListener) GetBlockChannel() <-chan *types.Block {
	return bl.queue
}

// Stop stops the listener
func (bl *BlockListener) Stop() {
	bl.cancel()
	if bl.client != nil {
		bl.client.Close()
	}
	if bl.wsClient != nil {
		bl.wsClient.Close()
	}
	close(bl.queue)
}
