package gateway

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

// RPCGateway handles RPC passthrough with caching
type RPCGateway struct {
	rpcURL    string
	httpClient *http.Client
	cache     Cache
	rateLimit RateLimiter
}

// Cache interface for caching RPC responses
type Cache interface {
	Get(key string) ([]byte, error)
	Set(key string, value []byte, ttl time.Duration) error
}

// RateLimiter interface for rate limiting
type RateLimiter interface {
	Allow(key string) bool
}

// NewRPCGateway creates a new RPC gateway
func NewRPCGateway(rpcURL string, cache Cache, rateLimit RateLimiter) *RPCGateway {
	return &RPCGateway{
		rpcURL: rpcURL,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
		cache:     cache,
		rateLimit: rateLimit,
	}
}

// RPCRequest represents a JSON-RPC request
type RPCRequest struct {
	JSONRPC string        `json:"jsonrpc"`
	Method  string        `json:"method"`
	Params  []interface{} `json:"params"`
	ID      int           `json:"id"`
}

// RPCResponse represents a JSON-RPC response
type RPCResponse struct {
	JSONRPC string      `json:"jsonrpc"`
	Result  interface{} `json:"result,omitempty"`
	Error   *RPCError   `json:"error,omitempty"`
	ID      int         `json:"id"`
}

// RPCError represents an RPC error
type RPCError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

// Call makes an RPC call with caching and rate limiting
func (g *RPCGateway) Call(ctx context.Context, method string, params []interface{}, cacheKey string, cacheTTL time.Duration) (*RPCResponse, error) {
	// Check cache first
	if cacheKey != "" {
		if cached, err := g.cache.Get(cacheKey); err == nil {
			var response RPCResponse
			if err := json.Unmarshal(cached, &response); err == nil {
				return &response, nil
			}
		}
	}

	// Check rate limit
	if !g.rateLimit.Allow("rpc") {
		return nil, fmt.Errorf("rate limit exceeded")
	}

	// Make RPC call
	req := RPCRequest{
		JSONRPC: "2.0",
		Method:  method,
		Params:  params,
		ID:      1,
	}

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

	httpReq, err := http.NewRequestWithContext(ctx, "POST", g.rpcURL, bytes.NewBuffer(reqBody))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := g.httpClient.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("RPC call failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("RPC returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	var rpcResp RPCResponse
	if err := json.Unmarshal(body, &rpcResp); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	if rpcResp.Error != nil {
		return nil, fmt.Errorf("RPC error: %s (code: %d)", rpcResp.Error.Message, rpcResp.Error.Code)
	}

	// Cache response if cache key provided
	if cacheKey != "" && rpcResp.Result != nil {
		if cacheData, err := json.Marshal(rpcResp); err == nil {
			g.cache.Set(cacheKey, cacheData, cacheTTL)
		}
	}

	return &rpcResp, nil
}

// GetBlockByNumber gets a block by number
func (g *RPCGateway) GetBlockByNumber(ctx context.Context, blockNumber string, includeTxs bool) (*RPCResponse, error) {
	cacheKey := fmt.Sprintf("block:%s:%v", blockNumber, includeTxs)
	return g.Call(ctx, "eth_getBlockByNumber", []interface{}{blockNumber, includeTxs}, cacheKey, 10*time.Second)
}

// GetBlockByHash gets a block by hash
func (g *RPCGateway) GetBlockByHash(ctx context.Context, blockHash string, includeTxs bool) (*RPCResponse, error) {
	cacheKey := fmt.Sprintf("block_hash:%s:%v", blockHash, includeTxs)
	return g.Call(ctx, "eth_getBlockByHash", []interface{}{blockHash, includeTxs}, cacheKey, 10*time.Second)
}

// GetTransactionByHash gets a transaction by hash
func (g *RPCGateway) GetTransactionByHash(ctx context.Context, txHash string) (*RPCResponse, error) {
	cacheKey := fmt.Sprintf("tx:%s", txHash)
	return g.Call(ctx, "eth_getTransactionByHash", []interface{}{txHash}, cacheKey, 30*time.Second)
}

// GetBalance gets an address balance
func (g *RPCGateway) GetBalance(ctx context.Context, address string, blockNumber string) (*RPCResponse, error) {
	if blockNumber == "" {
		blockNumber = "latest"
	}
	cacheKey := fmt.Sprintf("balance:%s:%s", address, blockNumber)
	return g.Call(ctx, "eth_getBalance", []interface{}{address, blockNumber}, cacheKey, 10*time.Second)
}

// GetBlockNumber gets the latest block number
func (g *RPCGateway) GetBlockNumber(ctx context.Context) (*RPCResponse, error) {
	return g.Call(ctx, "eth_blockNumber", []interface{}{}, "block_number", 5*time.Second)
}

// GetTransactionCount gets transaction count for an address
func (g *RPCGateway) GetTransactionCount(ctx context.Context, address string, blockNumber string) (*RPCResponse, error) {
	if blockNumber == "" {
		blockNumber = "latest"
	}
	cacheKey := fmt.Sprintf("tx_count:%s:%s", address, blockNumber)
	return g.Call(ctx, "eth_getTransactionCount", []interface{}{address, blockNumber}, cacheKey, 10*time.Second)
}

