package libp2pwebtransport

import (
	"errors"
	"fmt"
	"net"
	"strconv"

	ma "github.com/multiformats/go-multiaddr"
	manet "github.com/multiformats/go-multiaddr/net"
	"github.com/multiformats/go-multibase"
	"github.com/multiformats/go-multihash"
)

var webtransportMA = ma.StringCast("/quic-v1/webtransport")

func toWebtransportMultiaddr(na net.Addr) (ma.Multiaddr, error) {
	addr, err := manet.FromNetAddr(na)
	if err != nil {
		return nil, err
	}
	if _, err := addr.ValueForProtocol(ma.P_UDP); err != nil {
		return nil, errors.New("not a UDP address")
	}
	return addr.Encapsulate(webtransportMA), nil
}

func stringToWebtransportMultiaddr(str string) (ma.Multiaddr, error) {
	host, portStr, err := net.SplitHostPort(str)
	if err != nil {
		return nil, err
	}
	port, err := strconv.ParseInt(portStr, 10, 32)
	if err != nil {
		return nil, err
	}
	ip := net.ParseIP(host)
	if ip == nil {
		return nil, errors.New("failed to parse IP")
	}
	return toWebtransportMultiaddr(&net.UDPAddr{IP: ip, Port: int(port)})
}

func extractCertHashes(addr ma.Multiaddr) ([]multihash.DecodedMultihash, error) {
	certHashesStr := make([]string, 0, 2)
	ma.ForEach(addr, func(c ma.Component) bool {
		if c.Protocol().Code == ma.P_CERTHASH {
			certHashesStr = append(certHashesStr, c.Value())
		}
		return true
	})
	certHashes := make([]multihash.DecodedMultihash, 0, len(certHashesStr))
	for _, s := range certHashesStr {
		_, ch, err := multibase.Decode(s)
		if err != nil {
			return nil, fmt.Errorf("failed to multibase-decode certificate hash: %w", err)
		}
		dh, err := multihash.Decode(ch)
		if err != nil {
			return nil, fmt.Errorf("failed to multihash-decode certificate hash: %w", err)
		}
		certHashes = append(certHashes, *dh)
	}
	return certHashes, nil
}

func addrComponentForCert(hash []byte) (*ma.Component, error) {
	mh, err := multihash.Encode(hash, multihash.SHA2_256)
	if err != nil {
		return nil, err
	}
	certStr, err := multibase.Encode(multibase.Base58BTC, mh)
	if err != nil {
		return nil, err
	}
	return ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, certStr)
}

// IsWebtransportMultiaddr returns true if the given multiaddr is a well formed
// webtransport multiaddr. Returns the number of certhashes found.
func IsWebtransportMultiaddr(multiaddr ma.Multiaddr) (bool, int) {
	const (
		init = iota
		foundUDP
		foundQuicV1
		foundWebTransport
	)
	state := init
	certhashCount := 0

	ma.ForEach(multiaddr, func(c ma.Component) bool {
		switch c.Protocol().Code {
		case ma.P_UDP:
			if state == init {
				state = foundUDP
			}
		case ma.P_QUIC_V1:
			if state == foundUDP {
				state = foundQuicV1
			}
		case ma.P_WEBTRANSPORT:
			if state == foundQuicV1 {
				state = foundWebTransport
			}
		case ma.P_CERTHASH:
			if state == foundWebTransport {
				certhashCount++
			}
		}
		return true
	})
	return state == foundWebTransport, certhashCount
}
