package retrievalstoremgr

import (
	"errors"

	"github.com/filecoin-project/go-multistore"
	"github.com/filecoin-project/lotus/lib/blockstore"
	"github.com/filecoin-project/lotus/node/repo/importmgr"
	"github.com/ipfs/go-blockservice"
	offline "github.com/ipfs/go-ipfs-exchange-offline"
	ipldformat "github.com/ipfs/go-ipld-format"
	"github.com/ipfs/go-merkledag"
)

// RetrievalStore references a store for a retrieval deal
// which may or may not have a multistore ID associated with it
type RetrievalStore interface {
	StoreID() *multistore.StoreID
	DAGService() ipldformat.DAGService
}

// RetrievalStoreManager manages stores for retrieval deals, abstracting
// the underlying storage mechanism
type RetrievalStoreManager interface {
	NewStore() (RetrievalStore, error)
	ReleaseStore(RetrievalStore) error
}

// MultiStoreRetrievalStoreManager manages stores on top of the import manager
type MultiStoreRetrievalStoreManager struct {
	imgr *importmgr.Mgr
}

var _ RetrievalStoreManager = &MultiStoreRetrievalStoreManager{}

// NewMultiStoreRetrievalStoreManager returns a new multstore based RetrievalStoreManager
func NewMultiStoreRetrievalStoreManager(imgr *importmgr.Mgr) RetrievalStoreManager {
	return &MultiStoreRetrievalStoreManager{
		imgr: imgr,
	}
}

// NewStore creates a new store (uses multistore)
func (mrsm *MultiStoreRetrievalStoreManager) NewStore() (RetrievalStore, error) {
	storeID, store, err := mrsm.imgr.NewStore()
	if err != nil {
		return nil, err
	}
	return &multiStoreRetrievalStore{storeID, store}, nil
}

// ReleaseStore releases a store (uses multistore remove)
func (mrsm *MultiStoreRetrievalStoreManager) ReleaseStore(retrievalStore RetrievalStore) error {
	mrs, ok := retrievalStore.(*multiStoreRetrievalStore)
	if !ok {
		return errors.New("Cannot release this store type")
	}
	return mrsm.imgr.Remove(mrs.storeID)
}

type multiStoreRetrievalStore struct {
	storeID multistore.StoreID
	store   *multistore.Store
}

func (mrs *multiStoreRetrievalStore) StoreID() *multistore.StoreID {
	return &mrs.storeID
}

func (mrs *multiStoreRetrievalStore) DAGService() ipldformat.DAGService {
	return mrs.store.DAG
}

// BlockstoreRetrievalStoreManager manages a single blockstore as if it were multiple stores
type BlockstoreRetrievalStoreManager struct {
	bs blockstore.Blockstore
}

var _ RetrievalStoreManager = &BlockstoreRetrievalStoreManager{}

// NewBlockstoreRetrievalStoreManager returns a new blockstore based RetrievalStoreManager
func NewBlockstoreRetrievalStoreManager(bs blockstore.Blockstore) RetrievalStoreManager {
	return &BlockstoreRetrievalStoreManager{
		bs: bs,
	}
}

// NewStore creates a new store (just uses underlying blockstore)
func (brsm *BlockstoreRetrievalStoreManager) NewStore() (RetrievalStore, error) {
	return &blockstoreRetrievalStore{
		dagService: merkledag.NewDAGService(blockservice.New(brsm.bs, offline.Exchange(brsm.bs))),
	}, nil
}

// ReleaseStore for this implementation does nothing
func (brsm *BlockstoreRetrievalStoreManager) ReleaseStore(RetrievalStore) error {
	return nil
}

type blockstoreRetrievalStore struct {
	dagService ipldformat.DAGService
}

func (brs *blockstoreRetrievalStore) StoreID() *multistore.StoreID {
	return nil
}

func (brs *blockstoreRetrievalStore) DAGService() ipldformat.DAGService {
	return brs.dagService
}