package spa

import (
	"net/http"
	"sync"

	"github.com/iotaledger/goshimmer/packages/model/transactionmetadata"
	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
	"github.com/iotaledger/goshimmer/plugins/tangle"
	"github.com/labstack/echo"
	"github.com/pkg/errors"

	"github.com/iotaledger/iota.go/consts"
	"github.com/iotaledger/iota.go/guards"
	. "github.com/iotaledger/iota.go/trinary"
)

type ExplorerTx struct {
	Hash                     Hash   `json:"hash"`
	SignatureMessageFragment Trytes `json:"signature_message_fragment"`
	Address                  Hash   `json:"address"`
	Value                    int64  `json:"value"`
	Timestamp                uint   `json:"timestamp"`
	Trunk                    Hash   `json:"trunk"`
	Branch                   Hash   `json:"branch"`
	Solid                    bool   `json:"solid"`
	MWM                      int    `json:"mwm"`
}

func createExplorerTx(hash Hash, tx *value_transaction.ValueTransaction) (*ExplorerTx, error) {

	txMetadata, err := tangle.GetTransactionMetadata(hash, transactionmetadata.New)
	if err != nil {
		return nil, err
	}

	t := &ExplorerTx{
		Hash:                     tx.GetHash(),
		SignatureMessageFragment: tx.GetSignatureMessageFragment(),
		Address:                  tx.GetAddress(),
		Timestamp:                tx.GetTimestamp(),
		Value:                    tx.GetValue(),
		Trunk:                    tx.GetTrunkTransactionHash(),
		Branch:                   tx.GetBranchTransactionHash(),
		Solid:                    txMetadata.GetSolid(),
	}

	// compute mwm
	trits := MustTrytesToTrits(hash)
	var mwm int
	for i := len(trits) - 1; i >= 0; i-- {
		if trits[i] == 0 {
			mwm++
			continue
		}
		break
	}
	t.MWM = mwm
	return t, nil
}

type ExplorerAdress struct {
	Txs []*ExplorerTx `json:"txs"`
}

type SearchResult struct {
	Tx        *ExplorerTx     `json:"tx"`
	Address   *ExplorerAdress `json:"address"`
	Milestone *ExplorerTx     `json:"milestone"`
}

func setupExplorerRoutes(routeGroup *echo.Group) {

	routeGroup.GET("/tx/:hash", func(c echo.Context) error {
		t, err := findTransaction(c.Param("hash"))
		if err != nil {
			return err
		}
		return c.JSON(http.StatusOK, t)
	})

	routeGroup.GET("/addr/:hash", func(c echo.Context) error {
		addr, err := findAddress(c.Param("hash"))
		if err != nil {
			return err
		}
		return c.JSON(http.StatusOK, addr)
	})

	routeGroup.GET("/search/:search", func(c echo.Context) error {
		search := c.Param("search")
		result := &SearchResult{}

		if len(search) < 81 {
			return errors.Wrapf(ErrInvalidParameter, "search hash invalid: %s", search)
		}

		// auto. remove checksum
		search = search[:81]

		wg := sync.WaitGroup{}
		wg.Add(2)
		go func() {
			defer wg.Done()
			tx, err := findTransaction(search)
			if err == nil {
				result.Tx = tx
			}
		}()

		go func() {
			defer wg.Done()
			addr, err := findAddress(search)
			if err == nil {
				result.Address = addr
			}
		}()
		wg.Wait()

		return c.JSON(http.StatusOK, result)
	})
}

func findTransaction(hash Hash) (*ExplorerTx, error) {
	if !guards.IsTrytesOfExactLength(hash, consts.HashTrytesSize) {
		return nil, errors.Wrapf(ErrInvalidParameter, "hash invalid: %s", hash)
	}

	tx, err := tangle.GetTransaction(hash)
	if err != nil {
		return nil, err
	}
	if tx == nil {
		return nil, errors.Wrapf(ErrNotFound, "tx hash: %s", hash)
	}

	t, err := createExplorerTx(hash, tx)
	return t, err
}

func findAddress(hash Hash) (*ExplorerAdress, error) {
	if len(hash) > 81 {
		hash = hash[:81]
	}
	if !guards.IsTrytesOfExactLength(hash, consts.HashTrytesSize) {
		return nil, errors.Wrapf(ErrInvalidParameter, "hash invalid: %s", hash)
	}

	txHashes, err := tangle.ReadTransactionHashesForAddressFromDatabase(hash)
	if err != nil {
		return nil, ErrInternalError
	}

	if len(txHashes) == 0 {
		return nil, errors.Wrapf(ErrNotFound, "address %s not found", hash)
	}

	txs := make([]*ExplorerTx, 0, len(txHashes))
	for i := 0; i < len(txHashes); i++ {
		txHash := txHashes[i]

		tx, err := tangle.GetTransaction(hash)
		if err != nil {
			continue
		}
		if tx == nil {
			continue
		}
		expTx, err := createExplorerTx(txHash, tx)
		if err != nil {
			return nil, err
		}
		txs = append(txs, expTx)
	}

	return &ExplorerAdress{Txs: txs}, nil
}