package models

import (
	"fmt"
	"image/color"
	"net"
	"time"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/lightningnetwork/lnd/fn/v2"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/routing/route"
)

// Node represents an individual vertex/node within the channel graph.
// A node is connected to other nodes by one or more channel edges emanating
// from it. As the graph is directed, a node will also have an incoming edge
// attached to it for each outgoing edge.
type Node struct {
	// Version is the gossip version that this node was advertised on.
	Version lnwire.GossipVersion

	// PubKeyBytes is the raw bytes of the public key of the target node.
	PubKeyBytes [33]byte

	// LastUpdate is the last time the vertex information for this node has
	// been updated.
	LastUpdate time.Time

	// LastBlockHeight is the block height that timestamps the last update
	// we received for this node. This is only used if this is a V2 node
	// announcement.
	LastBlockHeight uint32

	// Address is the TCP address this node is reachable over.
	Addresses []net.Addr

	// Color is the selected color for the node.
	Color fn.Option[color.RGBA]

	// Alias is a nick-name for the node. The alias can be used to confirm
	// a node's identity or to serve as a short ID for an address book.
	Alias fn.Option[string]

	// AuthSigBytes is the raw signature under the advertised public key
	// which serves to authenticate the attributes announced by this node.
	AuthSigBytes []byte

	// Features is the list of protocol features supported by this node.
	Features *lnwire.FeatureVector

	// ExtraOpaqueData is the set of data that was appended to this
	// message, some of which we may not actually know how to iterate or
	// parse. By holding onto this data, we ensure that we're able to
	// properly validate the set of signatures that cover these new fields,
	// and ensure we're able to make upgrades to the network in a forwards
	// compatible manner. This is only used for V1 node announcements.
	ExtraOpaqueData []byte

	// ExtraSignedFields is a map of extra fields that are covered by the
	// node announcement's signature that we have not explicitly parsed.
	// This is only used for version 2 node announcements and beyond.
	ExtraSignedFields map[uint64][]byte
}

// NodeV1Fields houses the fields that are specific to a version 1 node
// announcement.
type NodeV1Fields struct {
	// Address is the TCP address this node is reachable over.
	Addresses []net.Addr

	// AuthSigBytes is the raw signature under the advertised public key
	// which serves to authenticate the attributes announced by this node.
	AuthSigBytes []byte

	// Features is the list of protocol features supported by this node.
	Features *lnwire.RawFeatureVector

	// Color is the selected color for the node.
	Color color.RGBA

	// Alias is a nick-name for the node. The alias can be used to confirm
	// a node's identity or to serve as a short ID for an address book.
	Alias string

	// LastUpdate is the last time the vertex information for this node has
	// been updated.
	LastUpdate time.Time

	// ExtraOpaqueData is the set of data that was appended to this
	// message, some of which we may not actually know how to iterate or
	// parse. By holding onto this data, we ensure that we're able to
	// properly validate the set of signatures that cover these new fields,
	// and ensure we're able to make upgrades to the network in a forwards
	// compatible manner.
	ExtraOpaqueData []byte
}

// NewV1Node creates a new version 1 node from the passed fields.
func NewV1Node(pub route.Vertex, n *NodeV1Fields) *Node {
	return &Node{
		Version:      lnwire.GossipVersion1,
		PubKeyBytes:  pub,
		Addresses:    n.Addresses,
		AuthSigBytes: n.AuthSigBytes,
		Features: lnwire.NewFeatureVector(
			n.Features, lnwire.Features,
		),
		Color:           fn.Some(n.Color),
		Alias:           fn.Some(n.Alias),
		LastUpdate:      n.LastUpdate,
		ExtraOpaqueData: n.ExtraOpaqueData,
	}
}

// NodeV2Fields houses the fields that are specific to a version 2 node
// announcement.
type NodeV2Fields struct {
	// LastBlockHeight is the block height that timestamps the last update
	// we received for this node.
	LastBlockHeight uint32

	// Address is the TCP address this node is reachable over.
	Addresses []net.Addr

	// Color is the selected color for the node.
	Color fn.Option[color.RGBA]

	// Alias is a nick-name for the node. The alias can be used to confirm
	// a node's identity or to serve as a short ID for an address book.
	Alias fn.Option[string]

	// Signature is the schnorr signature under the advertised public key
	// which serves to authenticate the attributes announced by this node.
	Signature []byte

	// Features is the list of protocol features supported by this node.
	Features *lnwire.RawFeatureVector

	// ExtraSignedFields is a map of extra fields that are covered by the
	// node announcement's signature that we have not explicitly parsed.
	ExtraSignedFields map[uint64][]byte
}

// NewV2Node creates a new version 2 node from the passed fields.
func NewV2Node(pub route.Vertex, n *NodeV2Fields) *Node {
	return &Node{
		Version:      lnwire.GossipVersion2,
		PubKeyBytes:  pub,
		Addresses:    n.Addresses,
		AuthSigBytes: n.Signature,
		Features: lnwire.NewFeatureVector(
			n.Features, lnwire.Features,
		),
		LastBlockHeight:   n.LastBlockHeight,
		Color:             n.Color,
		Alias:             n.Alias,
		LastUpdate:        time.Unix(0, 0),
		ExtraSignedFields: n.ExtraSignedFields,
	}
}

// NewV1ShellNode creates a new shell version 1 node.
func NewV1ShellNode(pubKey route.Vertex) *Node {
	return NewShellNode(lnwire.GossipVersion1, pubKey)
}

// NewShellNode creates a new shell node with the given gossip version and
// public key.
func NewShellNode(v lnwire.GossipVersion, pubKey route.Vertex) *Node {
	return &Node{
		Version:     v,
		PubKeyBytes: pubKey,
		Features:    lnwire.EmptyFeatureVector(),
		LastUpdate:  time.Unix(0, 0),
	}
}

// HaveAnnouncement returns true if we have received a node announcement for
// this node. We determine this by checking if we have a signature for the
// announcement.
func (n *Node) HaveAnnouncement() bool {
	return len(n.AuthSigBytes) > 0
}

// PubKey is the node's long-term identity public key. This key will be used to
// authenticated any advertisements/updates sent by the node.
func (n *Node) PubKey() (*btcec.PublicKey, error) {
	return btcec.ParsePubKey(n.PubKeyBytes[:])
}

// NodeAnnouncement retrieves the latest node announcement of the node.
func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1,
	error) {

	// Error out if we request the signed announcement, but we don't have
	// a signature for this announcement.
	if !n.HaveAnnouncement() && signed {
		return nil, fmt.Errorf("node does not have node announcement")
	}

	alias, err := lnwire.NewNodeAlias(n.Alias.UnwrapOr(""))
	if err != nil {
		return nil, err
	}

	nodeAnn := &lnwire.NodeAnnouncement1{
		Features:        n.Features.RawFeatureVector,
		NodeID:          n.PubKeyBytes,
		RGBColor:        n.Color.UnwrapOr(color.RGBA{}),
		Alias:           alias,
		Addresses:       n.Addresses,
		Timestamp:       uint32(n.LastUpdate.Unix()),
		ExtraOpaqueData: n.ExtraOpaqueData,
	}

	if !signed {
		return nodeAnn, nil
	}

	sig, err := lnwire.NewSigFromECDSARawSignature(n.AuthSigBytes)
	if err != nil {
		return nil, err
	}

	nodeAnn.Signature = sig

	return nodeAnn, nil
}

// NodeFromWireAnnouncement creates a Node instance from an
// lnwire.NodeAnnouncement1 message. The address list from msg.Addresses
// is copied verbatim, including legacy entries such as tor v2 onion
// addresses that lnd no longer produces itself. This is required so
// that Node.NodeAnnouncement can later reconstruct the exact byte
// sequence the remote peer signed, allowing signature verification and
// re-broadcast to succeed across restarts.
func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement1) *Node {
	timestamp := time.Unix(int64(msg.Timestamp), 0)

	return NewV1Node(
		msg.NodeID,
		&NodeV1Fields{
			LastUpdate:      timestamp,
			Addresses:       msg.Addresses,
			Alias:           msg.Alias.String(),
			AuthSigBytes:    msg.Signature.ToSignatureBytes(),
			Features:        msg.Features,
			Color:           msg.RGBColor,
			ExtraOpaqueData: msg.ExtraOpaqueData,
		},
	)
}
