package holders

import (
	"context"
	"log"
	"math/big"
	"strings"
	"time"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	db "github.com/gothinkster/golang-gin-realworld-example-app/common"
	"github.com/gothinkster/golang-gin-realworld-example-app/models"
)

// Swap(address account, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut, uint256 feeBasisPoints)
// account/tokenIn/tokenOut are indexed → topics[1..3]
// amountIn/amountOut/feeBasisPoints are non-indexed → data[0..95]
var swapEventTopic = crypto.Keccak256Hash([]byte("Swap(address,address,address,uint256,uint256,uint256)"))

// scanSwapEvents scans Swap events from all YT vaults and upserts into yt_swap_records.
func (s *Scanner) scanSwapEvents(ctx context.Context, fromBlock, toBlock uint64) error {
	if toBlock < fromBlock {
		return nil
	}

	database := db.GetDB()
	totalNew := 0

	// Cache block timestamps to avoid repeated RPC calls for the same block
	blockTimeCache := make(map[uint64]time.Time)

	for _, vault := range s.config.YTVaults {
		log.Printf("  [SwapScanner] %s blocks %d→%d", vault.Name, fromBlock, toBlock)

		logs, err := s.queryBlockRange(ctx, common.HexToAddress(vault.Address), swapEventTopic, fromBlock, toBlock)
		if err != nil {
			log.Printf("  [SwapScanner] Error scanning %s: %v", vault.Name, err)
			continue
		}

		for _, l := range logs {
			// Validate topics and data length
			if len(l.Topics) < 4 || len(l.Data) < 64 {
				continue
			}

			account  := common.BytesToAddress(l.Topics[1].Bytes()).Hex()
			tokenIn  := common.BytesToAddress(l.Topics[2].Bytes()).Hex()
			tokenOut := common.BytesToAddress(l.Topics[3].Bytes()).Hex()
			amountIn  := new(big.Int).SetBytes(l.Data[0:32])
			amountOut := new(big.Int).SetBytes(l.Data[32:64])

			// Get block time (cached per block number)
			blockTime, ok := blockTimeCache[l.BlockNumber]
			if !ok {
				blk, err := s.client.BlockByNumber(ctx, big.NewInt(int64(l.BlockNumber)))
				if err == nil {
					blockTime = time.Unix(int64(blk.Time()), 0)
					blockTimeCache[l.BlockNumber] = blockTime
				}
			}

			record := models.YTSwapRecord{
				TxHash:      l.TxHash.Hex(),
				LogIndex:    uint(l.Index),
				ChainID:     s.config.ChainID,
				BlockNumber: l.BlockNumber,
				BlockTime:   blockTime,
				VaultAddr:   vault.Address,
				Account:     account,
				TokenIn:     strings.ToLower(tokenIn),
				TokenOut:    strings.ToLower(tokenOut),
				AmountIn:    amountIn.String(),
				AmountOut:   amountOut.String(),
			}

			// Skip if already exists (idempotent)
			var existing models.YTSwapRecord
			res := database.Where("tx_hash = ? AND log_index = ?", record.TxHash, record.LogIndex).First(&existing)
			if res.Error == nil {
				continue
			}

			if err := database.Create(&record).Error; err != nil {
				if !strings.Contains(err.Error(), "Duplicate") {
					log.Printf("  [SwapScanner] Save failed %s:%d: %v", record.TxHash, record.LogIndex, err)
				}
				continue
			}
			totalNew++
		}
	}

	if totalNew > 0 {
		log.Printf("  [SwapScanner] Saved %d new swap records", totalNew)
	}
	return nil
}
