package tokens

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

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

// Extractor extracts token transfers from transaction logs
type Extractor struct {
	db      *pgxpool.Pool
	chainID int
}

// NewExtractor creates a new token extractor
func NewExtractor(db *pgxpool.Pool, chainID int) *Extractor {
	return &Extractor{
		db:      db,
		chainID: chainID,
	}
}

// ERC20 Transfer event signature: Transfer(address,address,uint256)
var ERC20TransferSignature = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")

// ERC721 Transfer event signature: Transfer(address,address,uint256)
var ERC721TransferSignature = ERC20TransferSignature

// ERC1155 TransferSingle event signature: TransferSingle(address,address,address,uint256,uint256)
var ERC1155TransferSingleSignature = common.HexToHash("0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62")

// ExtractTokenTransfers extracts token transfers from logs
func (e *Extractor) ExtractTokenTransfers(ctx context.Context, txHash common.Hash, blockNumber int64, logs []types.Log) error {
	for i, log := range logs {
		// Check for ERC20/ERC721 Transfer
		if len(log.Topics) == 3 && log.Topics[0] == ERC20TransferSignature {
			if err := e.extractERC20Transfer(ctx, txHash, blockNumber, i, log); err != nil {
				return fmt.Errorf("failed to extract ERC20 transfer: %w", err)
			}
		}

		// Check for ERC1155 TransferSingle
		if len(log.Topics) == 4 && log.Topics[0] == ERC1155TransferSingleSignature {
			if err := e.extractERC1155Transfer(ctx, txHash, blockNumber, i, log); err != nil {
				return fmt.Errorf("failed to extract ERC1155 transfer: %w", err)
			}
		}
	}

	return nil
}

// extractERC20Transfer extracts ERC20 transfer
func (e *Extractor) extractERC20Transfer(ctx context.Context, txHash common.Hash, blockNumber int64, logIndex int, log types.Log) error {
	if len(log.Topics) != 3 || len(log.Data) != 32 {
		return fmt.Errorf("invalid ERC20 transfer log")
	}

	from := common.BytesToAddress(log.Topics[1].Bytes())
	to := common.BytesToAddress(log.Topics[2].Bytes())
	amount := new(big.Int).SetBytes(log.Data)

	// Determine token type (ERC20 or ERC721)
	tokenType := "ERC20"
	if len(log.Data) == 0 {
		tokenType = "ERC721"
	}

	query := `
		INSERT INTO token_transfers (
			chain_id, transaction_hash, block_number, log_index,
			token_address, token_type, from_address, to_address, amount
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
		ON CONFLICT (chain_id, transaction_hash, log_index) DO NOTHING
	`

	_, err := e.db.Exec(ctx, query,
		e.chainID,
		txHash.Hex(),
		blockNumber,
		logIndex,
		log.Address.Hex(),
		tokenType,
		from.Hex(),
		to.Hex(),
		amount.String(),
	)

	if err != nil {
		return err
	}

	// Update token holder count
	return e.updateTokenStats(ctx, log.Address)
}

// extractERC1155Transfer extracts ERC1155 transfer
func (e *Extractor) extractERC1155Transfer(ctx context.Context, txHash common.Hash, blockNumber int64, logIndex int, log types.Log) error {
	if len(log.Topics) != 4 || len(log.Data) != 64 {
		return fmt.Errorf("invalid ERC1155 transfer log")
	}

	operator := common.BytesToAddress(log.Topics[1].Bytes())
	from := common.BytesToAddress(log.Topics[2].Bytes())
	to := common.BytesToAddress(log.Topics[3].Bytes())
	
	tokenID := new(big.Int).SetBytes(log.Data[:32])
	amount := new(big.Int).SetBytes(log.Data[32:])

	query := `
		INSERT INTO token_transfers (
			chain_id, transaction_hash, block_number, log_index,
			token_address, token_type, from_address, to_address, amount, token_id, operator
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
		ON CONFLICT (chain_id, transaction_hash, log_index) DO NOTHING
	`

	_, err := e.db.Exec(ctx, query,
		e.chainID,
		txHash.Hex(),
		blockNumber,
		logIndex,
		log.Address.Hex(),
		"ERC1155",
		from.Hex(),
		to.Hex(),
		amount.String(),
		tokenID.String(),
		operator.Hex(),
	)

	if err != nil {
		return err
	}

	return e.updateTokenStats(ctx, log.Address)
}

// updateTokenStats updates token statistics
func (e *Extractor) updateTokenStats(ctx context.Context, tokenAddress common.Address) error {
	// Count holders
	var holderCount int
	err := e.db.QueryRow(ctx, `
		SELECT COUNT(DISTINCT to_address)
		FROM token_transfers
		WHERE chain_id = $1 AND token_address = $2
	`, e.chainID, tokenAddress.Hex()).Scan(&holderCount)
	if err != nil {
		holderCount = 0
	}

	// Count transfers
	var transferCount int
	err = e.db.QueryRow(ctx, `
		SELECT COUNT(*)
		FROM token_transfers
		WHERE chain_id = $1 AND token_address = $2
	`, e.chainID, tokenAddress.Hex()).Scan(&transferCount)
	if err != nil {
		transferCount = 0
	}

	// Update token
	query := `
		INSERT INTO tokens (chain_id, address, type, holder_count, transfer_count)
		VALUES ($1, $2, 'ERC20', $3, $4)
		ON CONFLICT (chain_id, address) DO UPDATE SET
			holder_count = $3,
			transfer_count = $4,
			updated_at = NOW()
	`

	_, err = e.db.Exec(ctx, query, e.chainID, tokenAddress.Hex(), holderCount, transferCount)
	return err
}

