package netann

import (
	"bytes"
	"fmt"
	"time"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/davecgh/go-spew/spew"
	"github.com/lightningnetwork/lnd/graph/db/models"
	"github.com/lightningnetwork/lnd/keychain"
	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/pkg/errors"
)

const (
	// chanUpdate2MsgName is a string representing the name of the
	// ChannelUpdate2 message. This string will be used during the
	// construction of the tagged hash message to be signed when producing
	// the signature for the ChannelUpdate2 message.
	chanUpdate2MsgName = "channel_update_2"

	// chanUpdate2SigField is the name of the signature field of the
	// ChannelUpdate2 message. This string will be used during the
	// construction of the tagged hash message to be signed when producing
	// the signature for the ChannelUpdate2 message.
	chanUpdate2SigField = "signature"
)

// ErrUnableToExtractChanUpdate is returned when a channel update cannot be
// found for one of our active channels.
var ErrUnableToExtractChanUpdate = fmt.Errorf("unable to extract ChannelUpdate")

// ChannelUpdateModifier is a closure that makes in-place modifications to an
// lnwire.ChannelUpdate.
type ChannelUpdateModifier func(*lnwire.ChannelUpdate1)

// ChanUpdSetDisable is a functional option that sets the disabled channel flag
// if disabled is true, and clears the bit otherwise.
func ChanUpdSetDisable(disabled bool) ChannelUpdateModifier {
	return func(update *lnwire.ChannelUpdate1) {
		if disabled {
			// Set the bit responsible for marking a channel as
			// disabled.
			update.ChannelFlags |= lnwire.ChanUpdateDisabled
		} else {
			// Clear the bit responsible for marking a channel as
			// disabled.
			update.ChannelFlags &= ^lnwire.ChanUpdateDisabled
		}
	}
}

// ChanUpdSetTimestamp is a functional option that sets the timestamp of the
// update to the current time, or increments it if the timestamp is already in
// the future.
func ChanUpdSetTimestamp(update *lnwire.ChannelUpdate1) {
	newTimestamp := uint32(time.Now().Unix())
	if newTimestamp <= update.Timestamp {
		// Increment the prior value to ensure the timestamp
		// monotonically increases, otherwise the update won't
		// propagate.
		newTimestamp = update.Timestamp + 1
	}
	update.Timestamp = newTimestamp
}

// SignChannelUpdate applies the given modifiers to the passed
// lnwire.ChannelUpdate, then signs the resulting update. The provided update
// should be the most recent, valid update, otherwise the timestamp may not
// monotonically increase from the prior.
//
// NOTE: This method modifies the given update.
func SignChannelUpdate(signer lnwallet.MessageSigner, keyLoc keychain.KeyLocator,
	update *lnwire.ChannelUpdate1, mods ...ChannelUpdateModifier) error {

	// Apply the requested changes to the channel update.
	for _, modifier := range mods {
		modifier(update)
	}

	// Create the DER-encoded ECDSA signature over the message digest.
	sig, err := SignAnnouncement(signer, keyLoc, update)
	if err != nil {
		return err
	}

	// Parse the DER-encoded signature into a fixed-size 64-byte array.
	update.Signature, err = lnwire.NewSigFromSignature(sig)
	if err != nil {
		return err
	}

	return nil
}

// ExtractChannelUpdate attempts to retrieve a lnwire.ChannelUpdate message from
// an edge's info and a set of routing policies.
//
// NOTE: The passed policies can be nil.
func ExtractChannelUpdate(ownerPubKey []byte,
	info *models.ChannelEdgeInfo,
	policies ...*models.ChannelEdgePolicy) (
	*lnwire.ChannelUpdate1, error) {

	// Helper function to extract the owner of the given policy.
	owner := func(edge *models.ChannelEdgePolicy) []byte {
		var pubKey *btcec.PublicKey
		if edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
			pubKey, _ = info.NodeKey1()
		} else {
			pubKey, _ = info.NodeKey2()
		}

		// If pubKey was not found, just return nil.
		if pubKey == nil {
			return nil
		}

		return pubKey.SerializeCompressed()
	}

	// Extract the channel update from the policy we own, if any.
	for _, edge := range policies {
		if edge != nil && bytes.Equal(ownerPubKey, owner(edge)) {
			return ChannelUpdateFromEdge(info, edge)
		}
	}

	return nil, ErrUnableToExtractChanUpdate
}

// UnsignedChannelUpdateFromEdge reconstructs an unsigned ChannelUpdate from the
// given edge info and policy.
func UnsignedChannelUpdateFromEdge(info *models.ChannelEdgeInfo,
	policy *models.ChannelEdgePolicy) *lnwire.ChannelUpdate1 {

	return &lnwire.ChannelUpdate1{
		ChainHash:       info.ChainHash,
		ShortChannelID:  lnwire.NewShortChanIDFromInt(policy.ChannelID),
		Timestamp:       uint32(policy.LastUpdate.Unix()),
		ChannelFlags:    policy.ChannelFlags,
		MessageFlags:    policy.MessageFlags,
		TimeLockDelta:   policy.TimeLockDelta,
		HtlcMinimumMsat: policy.MinHTLC,
		HtlcMaximumMsat: policy.MaxHTLC,
		BaseFee:         uint32(policy.FeeBaseMSat),
		FeeRate:         uint32(policy.FeeProportionalMillionths),
		ExtraOpaqueData: policy.ExtraOpaqueData,
	}
}

// ChannelUpdateFromEdge reconstructs a signed ChannelUpdate from the given edge
// info and policy.
func ChannelUpdateFromEdge(info *models.ChannelEdgeInfo,
	policy *models.ChannelEdgePolicy) (*lnwire.ChannelUpdate1, error) {

	update := UnsignedChannelUpdateFromEdge(info, policy)

	var err error
	update.Signature, err = lnwire.NewSigFromECDSARawSignature(
		policy.SigBytes,
	)
	if err != nil {
		return nil, err
	}

	return update, nil
}

// ValidateChannelUpdateAnn validates the channel update announcement by
// checking (1) that the included signature covers the announcement and has been
// signed by the node's private key, and (2) that the announcement's message
// flags and optional fields are sane.
func ValidateChannelUpdateAnn(pubKey *btcec.PublicKey, capacity btcutil.Amount,
	a lnwire.ChannelUpdate) error {

	if err := ValidateChannelUpdateFields(capacity, a); err != nil {
		return err
	}

	return VerifyChannelUpdateSignature(a, pubKey)
}

// VerifyChannelUpdateSignature verifies that the channel update message was
// signed by the party with the given node public key.
func VerifyChannelUpdateSignature(msg lnwire.ChannelUpdate,
	pubKey *btcec.PublicKey) error {

	switch u := msg.(type) {
	case *lnwire.ChannelUpdate1:
		return verifyChannelUpdate1Signature(u, pubKey)
	case *lnwire.ChannelUpdate2:
		return verifyChannelUpdate2Signature(u, pubKey)
	default:
		return fmt.Errorf("unhandled implementation of "+
			"lnwire.ChannelUpdate: %T", msg)
	}
}

// verifyChannelUpdateSignature1 verifies that the channel update message was
// signed by the party with the given node public key.
func verifyChannelUpdate1Signature(msg *lnwire.ChannelUpdate1,
	pubKey *btcec.PublicKey) error {

	data, err := msg.DataToSign()
	if err != nil {
		return fmt.Errorf("unable to reconstruct message data: %w", err)
	}
	dataHash := chainhash.DoubleHashB(data)

	nodeSig, err := msg.Signature.ToSignature()
	if err != nil {
		return err
	}

	if !nodeSig.Verify(dataHash, pubKey) {
		return fmt.Errorf("invalid signature for channel update %v",
			spew.Sdump(msg))
	}

	return nil
}

// verifyChannelUpdateSignature2 verifies that the channel update message was
// signed by the party with the given node public key.
func verifyChannelUpdate2Signature(c *lnwire.ChannelUpdate2,
	pubKey *btcec.PublicKey) error {

	digest, err := chanUpdate2DigestToSign(c)
	if err != nil {
		return fmt.Errorf("unable to reconstruct message data: %w", err)
	}

	nodeSig, err := c.Signature.ToSignature()
	if err != nil {
		return err
	}

	if !nodeSig.Verify(digest, pubKey) {
		return fmt.Errorf("invalid signature for channel update %v",
			spew.Sdump(c))
	}

	return nil
}

// ValidateChannelUpdateFields validates a channel update's message flags and
// corresponding update fields.
func ValidateChannelUpdateFields(capacity btcutil.Amount,
	msg lnwire.ChannelUpdate) error {

	switch u := msg.(type) {
	case *lnwire.ChannelUpdate1:
		return validateChannelUpdate1Fields(capacity, u)
	case *lnwire.ChannelUpdate2:
		return validateChannelUpdate2Fields(capacity, u)
	default:
		return fmt.Errorf("unhandled implementation of "+
			"lnwire.ChannelUpdate: %T", msg)
	}
}

// validateChannelUpdate1Fields validates a channel update's message flags and
// corresponding update fields.
func validateChannelUpdate1Fields(capacity btcutil.Amount,
	msg *lnwire.ChannelUpdate1) error {

	// The maxHTLC flag is mandatory.
	if !msg.MessageFlags.HasMaxHtlc() {
		return errors.Errorf("max htlc flag not set for channel "+
			"update %v", spew.Sdump(msg))
	}

	maxHtlc := msg.HtlcMaximumMsat
	if maxHtlc == 0 || maxHtlc < msg.HtlcMinimumMsat {
		return errors.Errorf("invalid max htlc for channel "+
			"update %v", spew.Sdump(msg))
	}

	// For light clients, the capacity will not be set so we'll skip
	// checking whether the MaxHTLC value respects the channel's
	// capacity.
	capacityMsat := lnwire.NewMSatFromSatoshis(capacity)
	if capacityMsat != 0 && maxHtlc > capacityMsat {
		return errors.Errorf("max_htlc (%v) for channel update "+
			"greater than capacity (%v)", maxHtlc, capacityMsat)
	}

	return nil
}

// validateChannelUpdate2Fields validates a channel update's message flags and
// corresponding update fields.
func validateChannelUpdate2Fields(capacity btcutil.Amount,
	c *lnwire.ChannelUpdate2) error {

	maxHtlc := c.HTLCMaximumMsat.Val
	if maxHtlc == 0 || maxHtlc < c.HTLCMinimumMsat.Val {
		return fmt.Errorf("invalid max htlc for channel update %v",
			spew.Sdump(c))
	}

	// Checking whether the MaxHTLC value respects the channel's capacity.
	capacityMsat := lnwire.NewMSatFromSatoshis(capacity)
	if maxHtlc > capacityMsat {
		return fmt.Errorf("max_htlc (%v) for channel update greater "+
			"than capacity (%v)", maxHtlc, capacityMsat)
	}

	return nil
}

// ChanUpdate2DigestTag returns the tag to be used when signing the digest of
// a channel_update_2 message.
func ChanUpdate2DigestTag() []byte {
	return MsgTag(chanUpdate2MsgName, chanUpdate2SigField)
}

// chanUpdate2DigestToSign computes the digest of the ChannelUpdate2 message to
// be signed.
func chanUpdate2DigestToSign(c *lnwire.ChannelUpdate2) ([]byte, error) {
	data, err := c.DataToSign()
	if err != nil {
		return nil, err
	}

	hash := MsgHash(chanUpdate2MsgName, chanUpdate2SigField, data)

	return hash[:], nil
}
