package proxyd

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"strings"
	"sync"

	"github.com/ethereum/go-ethereum/log"
)

type RPCMethodHandler interface {
	GetRPCMethod(context.Context, *RPCReq) (*RPCRes, error)
	PutRPCMethod(context.Context, *RPCReq, *RPCRes) error
}

type StaticMethodHandler struct {
	cache     Cache
	m         sync.RWMutex
	filterGet func(*RPCReq) bool
	filterPut func(*RPCReq, *RPCRes) bool
}

func (e *StaticMethodHandler) key(req *RPCReq) string {
	// signature is the hashed json.RawMessage param contents
	h := sha256.New()
	h.Write(req.Params)
	signature := fmt.Sprintf("%x", h.Sum(nil))
	return strings.Join([]string{"cache", req.Method, signature}, ":")
}

func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) {
	if e.cache == nil {
		return nil, nil
	}
	if e.filterGet != nil && !e.filterGet(req) {
		return nil, nil
	}

	e.m.RLock()
	defer e.m.RUnlock()

	key := e.key(req)
	val, err := e.cache.Get(ctx, key)
	if err != nil {
		log.Error("error reading from cache", "key", key, "method", req.Method, "err", err)
		return nil, err
	}
	if val == "" {
		return nil, nil
	}

	var result interface{}
	if err := json.Unmarshal([]byte(val), &result); err != nil {
		log.Error("error unmarshalling value from cache", "key", key, "method", req.Method, "err", err)
		return nil, err
	}
	return &RPCRes{
		JSONRPC: req.JSONRPC,
		Result:  result,
		ID:      req.ID,
	}, nil
}

func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error {
	if e.cache == nil {
		return nil
	}
	// if there is a filter on get, we don't want to cache it because its irretrievable
	if e.filterGet != nil && !e.filterGet(req) {
		return nil
	}
	// response filter
	if e.filterPut != nil && !e.filterPut(req, res) {
		return nil
	}

	e.m.Lock()
	defer e.m.Unlock()

	key := e.key(req)
	value := mustMarshalJSON(res.Result)

	err := e.cache.Put(ctx, key, string(value))
	if err != nil {
		log.Error("error putting into cache", "key", key, "method", req.Method, "err", err)
		return err
	}
	return nil
}
