package rpc

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

	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
	"go.uber.org/zap"

	"github.com/genex/chain-indexer/internal/domain/entity"
	"github.com/genex/chain-indexer/internal/domain/repository"
)

// Compile-time check: EthChainClient implements repository.ChainClient.
var _ repository.ChainClient = (*EthChainClient)(nil)

// EthChainClient implements ChainClient using go-ethereum ethclient.
type EthChainClient struct {
	ethClient *ethclient.Client
	chainID   *big.Int
	logger    *zap.Logger
}

// NewEthChainClient connects to an EVM JSON-RPC endpoint and returns a ChainClient.
func NewEthChainClient(rpcURL string, logger *zap.Logger) (*EthChainClient, error) {
	client, err := ethclient.Dial(rpcURL)
	if err != nil {
		return nil, fmt.Errorf("failed to dial RPC %s: %w", rpcURL, err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	chainID, err := client.ChainID(ctx)
	if err != nil {
		client.Close()
		return nil, fmt.Errorf("failed to get chain ID: %w", err)
	}

	logger.Info("Connected to blockchain node",
		zap.String("rpcURL", rpcURL),
		zap.Int64("chainID", chainID.Int64()),
	)

	return &EthChainClient{
		ethClient: client,
		chainID:   chainID,
		logger:    logger,
	}, nil
}

// GetLatestBlockNumber returns the current head block number.
func (c *EthChainClient) GetLatestBlockNumber(ctx context.Context) (int64, error) {
	num, err := c.ethClient.BlockNumber(ctx)
	if err != nil {
		return 0, fmt.Errorf("eth_blockNumber failed: %w", err)
	}
	return int64(num), nil
}

// GetBlockByNumber fetches a full block and parses its transactions.
func (c *EthChainClient) GetBlockByNumber(ctx context.Context, height int64) (*entity.Block, []*entity.ChainTransaction, error) {
	block, err := c.ethClient.BlockByNumber(ctx, big.NewInt(height))
	if err != nil {
		return nil, nil, fmt.Errorf("eth_getBlockByNumber(%d) failed: %w", height, err)
	}

	blockTime := time.Unix(int64(block.Time()), 0)

	domainBlock, err := entity.NewBlock(
		block.Number().Int64(),
		block.Hash().Hex(),
		blockTime,
		len(block.Transactions()),
	)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create block entity: %w", err)
	}

	signer := types.LatestSignerForChainID(c.chainID)
	var txs []*entity.ChainTransaction

	for _, tx := range block.Transactions() {
		from, err := types.Sender(signer, tx)
		if err != nil {
			c.logger.Warn("Failed to recover tx sender",
				zap.String("txHash", tx.Hash().Hex()),
				zap.Error(err),
			)
			continue
		}

		to := ""
		if tx.To() != nil {
			to = tx.To().Hex()
		}

		// Get receipt for tx status
		status := "confirmed"
		receipt, err := c.ethClient.TransactionReceipt(ctx, tx.Hash())
		if err != nil {
			c.logger.Warn("Failed to get tx receipt, assuming confirmed",
				zap.String("txHash", tx.Hash().Hex()),
				zap.Error(err),
			)
		} else if receipt.Status == 0 {
			status = "failed"
		}

		chainTx, err := entity.NewChainTransaction(
			tx.Hash().Hex(),
			block.Number().Int64(),
			from.Hex(),
			to,
			tx.Value().String(),
			status,
			blockTime,
		)
		if err != nil {
			c.logger.Warn("Failed to create tx entity",
				zap.String("txHash", tx.Hash().Hex()),
				zap.Error(err),
			)
			continue
		}
		txs = append(txs, chainTx)
	}

	return domainBlock, txs, nil
}

// Close releases the underlying ethclient connection.
func (c *EthChainClient) Close() {
	if c.ethClient != nil {
		c.ethClient.Close()
		c.logger.Info("Blockchain RPC connection closed")
	}
}
