/**
 * controller.go
 *
 * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
 */

package discard

import (
	"errors"

	"git.code.tecnalia.com/ledgerbuilder/sdk/core/api"
	"git.code.tecnalia.com/ledgerbuilder/sdk/core/controller"
	"git.code.tecnalia.com/ledgerbuilder/sdk/core/fabric/protos"
	"git.code.tecnalia.com/ledgerbuilder/sdk/shared"

	"git.code.tecnalia.com/blockchain/hypercog/controller/stats"

	errs "git.code.tecnalia.com/traceblock/sdk/constants"
	"git.code.tecnalia.com/traceblock/sdk/controller/base"
	"git.code.tecnalia.com/traceblock/sdk/controller/split"
	"git.code.tecnalia.com/traceblock/sdk/model"
)

type DiscardController struct {
	base.TraceblockBaseController
}

var (
	errInvalidInputData     = errors.New("failed to read model")
	errInvalidDiscardQuantity = errors.New("quantity to discard is greater than the available quantity")
)

// constructor like function
func NewDiscardController() *DiscardController {
	ctl := new(DiscardController)
	ctl.LowLevelController = controller.NewLowLevelController()
	ctl.SetDataModelClosure(ctl.modelClosure)
	return ctl
}

func (c DiscardController) modelClosure(stub shared.LedgerBuildrStubInterface) shared.LedgerBuildrAsset {
	return NewDiscardParams()
}

func (c DiscardController) readTraceableAsset(stub shared.LedgerBuildrStubInterface, assetId string) (*model.TraceableAsset, error) {
	readedModel, err := c.ReadAbstractAssetAndReturn(stub, assetId, model.NewTraceableAsset())
	if err != nil {
		return nil, err
	}

	// asset data successfully readed
	asset, ok := readedModel.(*model.TraceableAsset)
	if !ok {
		return nil, errs.NotExistingAsset
	}

	return asset, nil
}

func (c DiscardController) readAsset(stub shared.LedgerBuildrStubInterface, requestAsset shared.LedgerBuildrAsset) (*model.TraceableAsset, error) {
	return c.readTraceableAsset(stub, requestAsset.GetID())
}


// HyperCOG specific discard operation
func  (c DiscardController) _discardAsset(stub shared.LedgerBuildrStubInterface, params *DiscardParams, discardableAsset *model.TraceableAsset) (*protos.Response, error) {
	if discardableAsset.Quantity < params.Quantity {
		return nil, errInvalidDiscardQuantity
	}

	var discardedStock *model.TraceableAsset
	generatedIds := make([]string, 2)

	if params.Quantity == discardableAsset.Quantity {
		discardedStock = discardableAsset
	} else {
		// discardableAsset.Quantity > discardParams.Quantity
		splitParams := new(split.SplitParams)
		splitParams.SetID(discardableAsset.GetID())
		splitParams.SplitConfig.ArchiveOld = true
		splitParams.SplitConfig.Bidirectional = true
		splitParams.SplitConfig.ChildCount = 2
		splitParams.SplitConfig.BaseAsset.AssetType = discardableAsset.AssetType
		splitParams.SplitConfig.BaseAsset.ArbitraryDataFields = discardableAsset.ArbitraryDataFields

		subAssets, err := split.SplitAsset(c.TraceblockBaseController, stub, discardableAsset, *splitParams)
		if err != nil {
			return nil, err
		}

		for i, c := range subAssets {
			generatedIds[i] = c.GetID()
		}

		newStock := subAssets[0]
		newStock.Quantity = discardableAsset.Quantity - params.Quantity
		newStock.Units = discardableAsset.Units

		respNewStock:= c.SaveAbstractAsset(stub, &newStock)
		if respNewStock.Status != shared.OK {
			return &respNewStock, nil
		}

		discardedStock = &subAssets[1]
		discardedStock.Quantity = params.Quantity
		discardedStock.Units = discardableAsset.Units
		discardedStock.Add("name", " Discarded")
	}

	err := stats.RegisterDiscard(stub, discardedStock.Quantity, discardedStock.Units)
	if err != nil {
		return nil, err
	}


	discardedStock.Add("status", "discarded")
	// Already marked as modified
	discardedStock.MarkAsDeleted()
	respDiscarted := c.SaveAbstractAsset(stub, discardedStock)
	if respDiscarted.Status != shared.OK {
		return &respDiscarted, nil
	}

	ret := api.NewAPIGenericResponsePtr("DiscardController:Discard", nil, generatedIds).SendResponse()
	return &ret, nil
}

func  (c DiscardController) _archiveAsset(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) (*protos.Response, error) {
	discardParams, ok := params.(*DiscardParams)
	if discardParams == nil || !ok {
		//failed when casting/fetching input data
		return nil, errInvalidInputData
	}

	discardableAsset, err := c.readAsset(stub, discardParams)
	if err != nil {
		return nil, err
	}

	status, ok := discardableAsset.Get("status")
	if status != "stocked" || status == "stocked" && discardParams.Quantity == 0 {
		// Done in MarkAssetAsDeleted middleware in normal operation
		discardableAsset.MarkModification(stub)
		discardableAsset.MarkAsDeleted()

		responseArchive := c.DeleteAbstractAsset(stub, discardableAsset)
		return &responseArchive, nil
	}

	// HyperCOG specific discard operation
	return c._discardAsset(stub, discardParams, discardableAsset)
}

func (c DiscardController) Discard(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) protos.Response {
	ret, err := c._archiveAsset(stub, params)
	if err != nil {
		return api.NewApiResponsePtr("DiscardController:Discard", err, nil).SendResponse()
	}
	return *ret
}