package market

import (
	"bytes"
	"context"

	"github.com/ipfs/go-datastore"
	"github.com/ipfs/go-datastore/namespace"
	dsq "github.com/ipfs/go-datastore/query"

	"github.com/filecoin-project/go-address"
	cborrpc "github.com/filecoin-project/go-cbor-util"

	"github.com/filecoin-project/lotus/node/modules/dtypes"
)

const dsKeyAddr = "Addr"

type Store struct {
	ds datastore.Batching
}

func newStore(ds dtypes.MetadataDS) *Store {
	ds = namespace.Wrap(ds, datastore.NewKey("/fundmgr/"))
	return &Store{
		ds: ds,
	}
}

// save the state to the datastore
func (ps *Store) save(ctx context.Context, state *FundedAddressState) error {
	k := dskeyForAddr(state.Addr)

	b, err := cborrpc.Dump(state)
	if err != nil {
		return err
	}

	return ps.ds.Put(ctx, k, b)
}

// get the state for the given address
func (ps *Store) get(ctx context.Context, addr address.Address) (*FundedAddressState, error) {
	k := dskeyForAddr(addr)

	data, err := ps.ds.Get(ctx, k)
	if err != nil {
		return nil, err
	}

	var state FundedAddressState
	err = cborrpc.ReadCborRPC(bytes.NewReader(data), &state)
	if err != nil {
		return nil, err
	}
	return &state, nil
}

// forEach calls iter with each address in the datastore
func (ps *Store) forEach(ctx context.Context, iter func(*FundedAddressState)) error {
	res, err := ps.ds.Query(ctx, dsq.Query{Prefix: dsKeyAddr})
	if err != nil {
		return err
	}
	defer res.Close() //nolint:errcheck

	for {
		res, ok := res.NextSync()
		if !ok {
			break
		}

		if res.Error != nil {
			return err
		}

		var stored FundedAddressState
		if err := stored.UnmarshalCBOR(bytes.NewReader(res.Value)); err != nil {
			return err
		}

		iter(&stored)
	}

	return nil
}

// The datastore key used to identify the address state
func dskeyForAddr(addr address.Address) datastore.Key {
	return datastore.KeyWithNamespaces([]string{dsKeyAddr, addr.String()})
}