package htlcswitch

import (
	"bytes"
	"io"

	"github.com/go-errors/errors"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/contractcourt"
	"github.com/lightningnetwork/lnd/kvdb"
	"github.com/lightningnetwork/lnd/lnwire"
)

var (
	// resBucketKey is used for the root level bucket that stores the
	// CircuitKey -> ResolutionMsg mapping.
	resBucketKey = []byte("resolution-store-bucket-key")

	// errResMsgNotFound is used to let callers know that the resolution
	// message was not found for the given CircuitKey. This is used in the
	// checkResolutionMsg function.
	errResMsgNotFound = errors.New("resolution message not found")
)

// resolutionStore contains ResolutionMsgs received from the contractcourt. The
// Switch deletes these from the store when the underlying circuit has been
// removed via DeleteCircuits. If the circuit hasn't been deleted, the Switch
// will dispatch the ResolutionMsg to a link if this was a multi-hop HTLC or to
// itself if the Switch initiated the payment.
type resolutionStore struct {
	backend kvdb.Backend
}

func newResolutionStore(db kvdb.Backend) *resolutionStore {
	return &resolutionStore{
		backend: db,
	}
}

// addResolutionMsg persists a ResolutionMsg to the resolutionStore.
func (r *resolutionStore) addResolutionMsg(
	resMsg *contractcourt.ResolutionMsg) error {

	// The outKey will be the database key.
	outKey := &CircuitKey{
		ChanID: resMsg.SourceChan,
		HtlcID: resMsg.HtlcIndex,
	}

	var resBuf bytes.Buffer
	if err := serializeResolutionMsg(&resBuf, resMsg); err != nil {
		return err
	}

	err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error {
		resBucket, err := tx.CreateTopLevelBucket(resBucketKey)
		if err != nil {
			return err
		}

		return resBucket.Put(outKey.Bytes(), resBuf.Bytes())
	}, func() {})
	if err != nil {
		return err
	}

	return nil
}

// checkResolutionMsg returns nil if the resolution message is found in the
// store. It returns an error if no resolution message was found for the
// passed outKey or if a database error occurred.
func (r *resolutionStore) checkResolutionMsg(outKey *CircuitKey) error {
	err := kvdb.View(r.backend, func(tx kvdb.RTx) error {
		resBucket := tx.ReadBucket(resBucketKey)
		if resBucket == nil {
			// Return an error if the bucket doesn't exist.
			return errResMsgNotFound
		}

		msg := resBucket.Get(outKey.Bytes())
		if msg == nil {
			// Return the not found error since no message exists
			// for this CircuitKey.
			return errResMsgNotFound
		}

		// Return nil to indicate that the message was found.
		return nil
	}, func() {})
	if err != nil {
		return err
	}

	return nil
}

// fetchAllResolutionMsg returns a slice of all stored ResolutionMsgs. This is
// used by the Switch on start-up.
func (r *resolutionStore) fetchAllResolutionMsg() (
	[]*contractcourt.ResolutionMsg, error) {

	var msgs []*contractcourt.ResolutionMsg

	err := kvdb.View(r.backend, func(tx kvdb.RTx) error {
		resBucket := tx.ReadBucket(resBucketKey)
		if resBucket == nil {
			return nil
		}

		return resBucket.ForEach(func(k, v []byte) error {
			kr := bytes.NewReader(k)
			outKey := &CircuitKey{}
			if err := outKey.Decode(kr); err != nil {
				return err
			}

			vr := bytes.NewReader(v)
			resMsg, err := deserializeResolutionMsg(vr)
			if err != nil {
				return err
			}

			// Set the CircuitKey values on the ResolutionMsg.
			resMsg.SourceChan = outKey.ChanID
			resMsg.HtlcIndex = outKey.HtlcID

			msgs = append(msgs, resMsg)
			return nil
		})
	}, func() {
		msgs = nil
	})
	if err != nil {
		return nil, err
	}

	return msgs, nil
}

// deleteResolutionMsg removes a ResolutionMsg with the passed-in CircuitKey.
func (r *resolutionStore) deleteResolutionMsg(outKey *CircuitKey) error {
	err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error {
		resBucket, err := tx.CreateTopLevelBucket(resBucketKey)
		if err != nil {
			return err
		}

		return resBucket.Delete(outKey.Bytes())
	}, func() {})
	return err
}

// serializeResolutionMsg writes part of a ResolutionMsg to the passed
// io.Writer.
func serializeResolutionMsg(w io.Writer,
	resMsg *contractcourt.ResolutionMsg) error {

	isFail := resMsg.Failure != nil

	if err := channeldb.WriteElement(w, isFail); err != nil {
		return err
	}

	// If this is a failure message, then we're done serializing.
	if isFail {
		return nil
	}

	// Else this is a settle message, and we need to write the preimage.
	return channeldb.WriteElement(w, *resMsg.PreImage)
}

// deserializeResolutionMsg reads part of a ResolutionMsg from the passed
// io.Reader.
func deserializeResolutionMsg(r io.Reader) (*contractcourt.ResolutionMsg,
	error) {

	resMsg := &contractcourt.ResolutionMsg{}
	var isFail bool

	if err := channeldb.ReadElements(r, &isFail); err != nil {
		return nil, err
	}

	// If a failure resolution msg was stored, set the Failure field.
	if isFail {
		failureMsg := &lnwire.FailPermanentChannelFailure{}
		resMsg.Failure = failureMsg
		return resMsg, nil
	}

	var preimage [32]byte
	resMsg.PreImage = &preimage

	// Else this is a settle resolution msg and we will read the preimage.
	if err := channeldb.ReadElement(r, resMsg.PreImage); err != nil {
		return nil, err
	}

	return resMsg, nil
}
