package traces

import (
	"context"
	"encoding/json"
	"fmt"

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

// Tracer extracts and stores transaction traces
type Tracer struct {
	db      *pgxpool.Pool
	client  *ethclient.Client
	chainID int
}

// NewTracer creates a new tracer
func NewTracer(db *pgxpool.Pool, client *ethclient.Client, chainID int) *Tracer {
	return &Tracer{
		db:      db,
		client:  client,
		chainID: chainID,
	}
}

// Trace represents a transaction trace
type Trace struct {
	TransactionHash common.Hash
	BlockNumber     int64
	Traces          []CallTrace
}

// CallTrace represents a single call in a trace
type CallTrace struct {
	Type         string
	From         common.Address
	To           common.Address
	Value        string
	Gas          uint64
	GasUsed      uint64
	Input        string
	Output       string
	Error        string
	Calls        []CallTrace
}

// ExtractTrace extracts trace for a transaction
func (t *Tracer) ExtractTrace(ctx context.Context, txHash common.Hash, blockNumber int64) error {
	// Call trace_block RPC method
	var result json.RawMessage
	err := t.client.Client().CallContext(ctx, &result, "debug_traceTransaction", txHash.Hex(), map[string]interface{}{
		"tracer": "callTracer",
		"tracerConfig": map[string]interface{}{
			"withLog": true,
		},
	})

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

	// Parse trace
	var trace CallTrace
	if err := json.Unmarshal(result, &trace); err != nil {
		return fmt.Errorf("failed to parse trace: %w", err)
	}

	// Store trace
	return t.storeTrace(ctx, txHash, blockNumber, trace)
}

// storeTrace stores trace in database
func (t *Tracer) storeTrace(ctx context.Context, txHash common.Hash, blockNumber int64, trace CallTrace) error {
	// Create traces table if it doesn't exist
	// For now, store as JSONB
	query := `
		CREATE TABLE IF NOT EXISTS traces (
			chain_id INTEGER NOT NULL,
			transaction_hash VARCHAR(66) NOT NULL,
			block_number BIGINT NOT NULL,
			trace_data JSONB NOT NULL,
			created_at TIMESTAMP DEFAULT NOW(),
			PRIMARY KEY (chain_id, transaction_hash)
		) PARTITION BY LIST (chain_id)
	`

	// Ensure the table exists. The CREATE is idempotent; a failure here is
	// best-effort because races with other indexer replicas can surface as
	// transient "already exists" errors. The follow-up INSERT will surface
	// any real schema problem.
	if _, err := t.db.Exec(ctx, query); err != nil {
		_ = err
	}

	// Insert trace
	insertQuery := `
		INSERT INTO traces (chain_id, transaction_hash, block_number, trace_data)
		VALUES ($1, $2, $3, $4)
		ON CONFLICT (chain_id, transaction_hash) DO UPDATE SET
			trace_data = $4,
			created_at = NOW()
	`

	traceJSON, err := json.Marshal(trace)
	if err != nil {
		return fmt.Errorf("failed to marshal trace: %w", err)
	}

	_, err = t.db.Exec(ctx, insertQuery, t.chainID, txHash.Hex(), blockNumber, traceJSON)
	return err
}

