package track2

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

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/jackc/pgx/v5/pgxpool"
)

// TokenIndexer indexes ERC-20 token transfers for Track 2
type TokenIndexer struct {
	db     *pgxpool.Pool
	client *ethclient.Client
	chainID int
}

// NewTokenIndexer creates a new token indexer
func NewTokenIndexer(db *pgxpool.Pool, client *ethclient.Client, chainID int) *TokenIndexer {
	return &TokenIndexer{
		db:      db,
		client:  client,
		chainID: chainID,
	}
}

// ERC20TransferEventSignature is the signature for ERC-20 Transfer event
const ERC20TransferEventSignature = "Transfer(address,address,uint256)"

// IndexTokenTransfers indexes token transfers from a transaction receipt
func (ti *TokenIndexer) IndexTokenTransfers(ctx context.Context, receipt *types.Receipt, blockNumber uint64, blockHash common.Hash, timestamp time.Time) error {
	// Parse Transfer event signature
	transferEventSig := common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") // keccak256("Transfer(address,address,uint256)")

	for _, log := range receipt.Logs {
		// Check if this is a Transfer event
		if len(log.Topics) != 3 || log.Topics[0] != transferEventSig {
			continue
		}

		// Extract token contract, from, to, and value
		tokenContract := log.Address.Hex()
		from := common.BytesToAddress(log.Topics[1].Bytes()).Hex()
		to := common.BytesToAddress(log.Topics[2].Bytes()).Hex()

		// Decode value from data
		value := new(big.Int).SetBytes(log.Data)

		// Insert token transfer
		insertQuery := `
			INSERT INTO token_transfers (
				chain_id, transaction_hash, log_index, block_number, block_hash,
				timestamp, token_contract, from_address, to_address, value
			) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
			ON CONFLICT (chain_id, transaction_hash, log_index) DO NOTHING
		`

		_, err := ti.db.Exec(ctx, insertQuery,
			ti.chainID,
			receipt.TxHash.Hex(),
			log.Index,
			blockNumber,
			blockHash.Hex(),
			timestamp,
			tokenContract,
			from,
			to,
			value.String(),
		)

		if err != nil {
			return fmt.Errorf("failed to insert token transfer: %w", err)
		}

		// Update token balances
		ti.updateTokenBalances(ctx, tokenContract, from, to, value)
	}

	return nil
}

// updateTokenBalances updates token balances for addresses
func (ti *TokenIndexer) updateTokenBalances(ctx context.Context, tokenContract, from, to string, value *big.Int) {
	// Decrease from balance
	if from != "" && from != "0x0000000000000000000000000000000000000000" {
		updateFromQuery := `
			INSERT INTO token_balances (address, token_contract, chain_id, balance, last_updated_timestamp)
			VALUES ($1, $2, $3, 0, NOW())
			ON CONFLICT (address, token_contract, chain_id) DO UPDATE SET
				balance = GREATEST(0, token_balances.balance - $4::numeric),
				last_updated_timestamp = NOW(),
				updated_at = NOW()
		`
		ti.db.Exec(ctx, updateFromQuery, strings.ToLower(from), strings.ToLower(tokenContract), ti.chainID, value.String())
	}

	// Increase to balance
	if to != "" && to != "0x0000000000000000000000000000000000000000" {
		updateToQuery := `
			INSERT INTO token_balances (address, token_contract, chain_id, balance, last_updated_timestamp)
			VALUES ($1, $2, $3, $4, NOW())
			ON CONFLICT (address, token_contract, chain_id) DO UPDATE SET
				balance = token_balances.balance + $4::numeric,
				last_updated_timestamp = NOW(),
				updated_at = NOW()
		`
		ti.db.Exec(ctx, updateToQuery, strings.ToLower(to), strings.ToLower(tokenContract), ti.chainID, value.String())
	}
}

// IndexBlockTokenTransfers indexes all token transfers in a block
func (ti *TokenIndexer) IndexBlockTokenTransfers(ctx context.Context, blockNumber uint64) error {
	block, err := ti.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber)))
	if err != nil {
		return fmt.Errorf("failed to get block: %w", err)
	}

	for _, tx := range block.Transactions() {
		receipt, err := ti.client.TransactionReceipt(ctx, tx.Hash())
		if err != nil {
			continue
		}

		if err := ti.IndexTokenTransfers(ctx, receipt, blockNumber, block.Hash(), time.Unix(int64(block.Time()), 0)); err != nil {
			fmt.Printf("Failed to index token transfers for tx %s: %v\n", tx.Hash().Hex(), err)
		}
	}

	return nil
}

