package cidlink

import (
	"fmt"

	cid "github.com/ipfs/go-cid"
	"github.com/ipld/go-ipld-prime/datamodel"
	multihash "github.com/multiformats/go-multihash"
)

var (
	_ datamodel.Link          = Link{}
	_ datamodel.LinkPrototype = LinkPrototype{}
)

// Link implements the datamodel.Link interface using a CID.
// See https://github.com/ipfs/go-cid for more information about CIDs.
//
// When using this value, typically you'll use it as `Link`, and not `*Link`.
// This includes when handling the value as an `datamodel.Link` interface -- the non-pointer form is typically preferable.
// This is because the datamodel.Link interface is often desirable to be able to use as a golang map key,
// and in that context, pointers would not result in the desired behavior.
type Link struct {
	cid.Cid
}

func (lnk Link) Prototype() datamodel.LinkPrototype {
	return LinkPrototype{lnk.Cid.Prefix()}
}
func (lnk Link) String() string {
	return lnk.Cid.String()
}
func (lnk Link) Binary() string {
	return lnk.Cid.KeyString()
}

type LinkPrototype struct {
	cid.Prefix
}

func (lp LinkPrototype) BuildLink(hashsum []byte) datamodel.Link {
	// Does this method body look surprisingly complex?  I agree.
	//  We actually have to do all this work.  The go-cid package doesn't expose a constructor that just lets us directly set the bytes and the prefix numbers next to each other.
	//  No, `cid.Prefix.Sum` is not the method you are looking for: that expects the whole data body.
	//  Most of the logic here is the same as the body of `cid.Prefix.Sum`; we just couldn't get at the relevant parts without copypasta.
	//  There is also some logic that's sort of folded in from the go-multihash module.  This is really a mess.
	//  The go-cid package needs review.  So does go-multihash.  Their responsibilies are not well compartmentalized and they don't play well with other stdlib golang interfaces.
	p := lp.Prefix

	length := p.MhLength
	if p.MhType == multihash.IDENTITY {
		length = -1
	}
	if p.Version == 0 && (p.MhType != multihash.SHA2_256 ||
		(p.MhLength != 32 && p.MhLength != -1)) {
		panic(fmt.Errorf("invalid cid v0 prefix"))
	}

	if length != -1 {
		hashsum = hashsum[:p.MhLength]
	}

	mh, err := multihash.Encode(hashsum, p.MhType)
	if err != nil {
		panic(err) // No longer possible, but multihash still returns an error for legacy reasons.
	}

	switch lp.Prefix.Version {
	case 0:
		return Link{cid.NewCidV0(mh)}
	case 1:
		return Link{cid.NewCidV1(p.Codec, mh)}
	default:
		panic(fmt.Errorf("invalid cid version"))
	}
}
