package network

import (
	"crypto/ed25519"
	"encoding/base64"
	"errors"
	"fmt"
	"net"
	"strings"

	"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
)

var (
	ErrInvalidFormat = errors.New("address must be in the format 'dsfx://<ip>:<port>#<publicKey>'")
)

// Addr is a wrapper around net.Addr that adds a public key to the address. This
// means that all connections between two nodes can be verified by their public
// keys.
type Addr struct {
	network   string
	ip        net.IP
	port      int
	publicKey ed25519.PublicKey
}

// NewAddr creates a new Addr.
func NewAddr(ip net.IP, port int, publicKey ed25519.PublicKey) *Addr {
	network := "dsfx"
	return &Addr{network, ip, port, publicKey}
}

func ParseAddr(addrRaw string) (*Addr, error) {
	addrWoNet := strings.ReplaceAll(addrRaw, "dsfx://", "")
	parts := strings.Split(addrWoNet, "#")
	if len(parts) != 2 {
		return nil, ErrInvalidFormat
	}

	addr := parts[0]
	publicKeyBase64 := parts[1]

	parts = strings.Split(addr, ":")
	if len(parts) != 2 {
		return nil, ErrInvalidFormat
	}
	ip := net.ParseIP(parts[0])
	if ip == nil {
		return nil, ErrInvalidFormat
	}

	port, err := net.LookupPort("tcp", parts[1])
	if err != nil {
		return nil, ErrInvalidFormat
	}

	publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKeyBase64)
	if err != nil {
		return nil, ErrInvalidFormat
	}

	publicKey, err := identity.ImportPublicKey(publicKeyBytes)
	if err != nil {
		return nil, ErrInvalidFormat
	}

	network := "dsfx"
	return &Addr{network, ip, port, publicKey}, nil
}

// FromTCPAddr creates a new Addr from a net.TCPAddr.
func FromTCPAddr(addr *net.TCPAddr, publicKey ed25519.PublicKey) *Addr {
	return &Addr{
		network:   "dsfx",
		ip:        addr.IP,
		port:      addr.Port,
		publicKey: publicKey,
	}
}

// Network implements net.Addr.
func (a *Addr) Network() string {
	return a.network
}

// String implements net.Addr.
func (a *Addr) String() string {
	exported, _ := identity.ExportPublicKey(a.publicKey)
	exportedBase64 := base64.StdEncoding.EncodeToString(exported)
	return fmt.Sprintf("%s://%s:%d#%s", a.network, a.ip, a.port, exportedBase64)
}

// IP returns the IP address of the Addr.
func (a *Addr) IP() net.IP {
	return a.ip
}

// Port returns the port of the Addr.
func (a *Addr) Port() int {
	return a.port
}

// PublicKey returns the public key of the Addr.
func (a *Addr) PublicKey() ed25519.PublicKey {
	return a.publicKey
}

// TCPAddr returns a net.TCPAddr for the Addr.
func (a *Addr) TCPAddr() *net.TCPAddr {
	return &net.TCPAddr{
		IP:   a.ip,
		Port: a.port,
	}
}