package track2

import (
	"context"
	"fmt"
	"math/big"
	"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"
)

// TransactionIndexer indexes transactions for Track 2
type TransactionIndexer struct {
	db     *pgxpool.Pool
	client *ethclient.Client
	chainID int
}

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

// IndexTransaction indexes a single transaction
func (ti *TransactionIndexer) IndexTransaction(ctx context.Context, txHash common.Hash, blockNumber uint64, txIndex uint) error {
	// Check if transaction already indexed
	var exists bool
	checkQuery := `SELECT EXISTS(SELECT 1 FROM transactions WHERE chain_id = $1 AND hash = $2)`
	err := ti.db.QueryRow(ctx, checkQuery, ti.chainID, txHash.Hex()).Scan(&exists)
	if err != nil {
		return fmt.Errorf("failed to check transaction existence: %w", err)
	}

	if exists {
		return nil // Already indexed
	}

	// Get transaction receipt
	receipt, err := ti.client.TransactionReceipt(ctx, txHash)
	if err != nil {
		return fmt.Errorf("failed to get transaction receipt: %w", err)
	}

	// Get transaction
	tx, _, err := ti.client.TransactionByHash(ctx, txHash)
	if err != nil {
		return fmt.Errorf("failed to get transaction: %w", err)
	}

	// Get block for timestamp
	block, err := ti.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber)))
	if err != nil {
		return fmt.Errorf("failed to get block: %w", err)
	}

	// Determine status
	status := "success"
	if receipt.Status == 0 {
		status = "failed"
	}

	// Get sender address
	signer := types.NewEIP155Signer(big.NewInt(int64(ti.chainID)))
	fromAddr, err := types.Sender(signer, tx)
	if err != nil {
		return fmt.Errorf("failed to get sender address: %w", err)
	}

	// Insert transaction
	insertQuery := `
		INSERT INTO transactions (
			chain_id, hash, block_number, block_hash, transaction_index,
			from_address, to_address, value, gas, gas_price, gas_used,
			cumulative_gas_used, status, timestamp
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
		ON CONFLICT (chain_id, hash) DO NOTHING
	`

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

	_, err = ti.db.Exec(ctx, insertQuery,
		ti.chainID,
		txHash.Hex(),
		blockNumber,
		block.Hash().Hex(),
		txIndex,
		fromAddr.Hex(),
		toAddr,
		tx.Value().String(),
		tx.Gas(),
		tx.GasPrice().String(),
		receipt.GasUsed,
		receipt.CumulativeGasUsed,
		status,
		time.Unix(int64(block.Time()), 0),
	)

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

	// Update address statistics
	ti.updateAddressStats(ctx, fromAddr.Hex(), toAddr, tx.Value())

	return nil
}

// IndexBlockTransactions indexes all transactions in a block
func (ti *TransactionIndexer) IndexBlockTransactions(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 i, tx := range block.Transactions() {
		if err := ti.IndexTransaction(ctx, tx.Hash(), blockNumber, uint(i)); err != nil {
			fmt.Printf("Failed to index transaction %s: %v\n", tx.Hash().Hex(), err)
		}
	}

	return nil
}

// updateAddressStats updates address statistics
func (ti *TransactionIndexer) updateAddressStats(ctx context.Context, from, to string, value *big.Int) {
	// Update from address
	if from != "" {
		updateQuery := `
			INSERT INTO addresses (address, chain_id, tx_count_sent, total_sent_wei, first_seen_timestamp, last_seen_timestamp)
			VALUES ($1, $2, 1, $3, NOW(), NOW())
			ON CONFLICT (address) DO UPDATE SET
				tx_count_sent = addresses.tx_count_sent + 1,
				total_sent_wei = addresses.total_sent_wei + $3::numeric,
				last_seen_timestamp = NOW(),
				updated_at = NOW()
		`
		ti.db.Exec(ctx, updateQuery, from, ti.chainID, value.String())
	}

	// Update to address
	if to != "" {
		updateQuery := `
			INSERT INTO addresses (address, chain_id, tx_count_received, total_received_wei, first_seen_timestamp, last_seen_timestamp)
			VALUES ($1, $2, 1, $3, NOW(), NOW())
			ON CONFLICT (address) DO UPDATE SET
				tx_count_received = addresses.tx_count_received + 1,
				total_received_wei = addresses.total_received_wei + $3::numeric,
				last_seen_timestamp = NOW(),
				updated_at = NOW()
		`
		ti.db.Exec(ctx, updateQuery, to, ti.chainID, value.String())
	}
}

