package bech32encoding

import (
	"bytes"
	"encoding/binary"

	"lol.mleku.dev/chk"
	"lol.mleku.dev/errorf"
	"lol.mleku.dev/log"
	"next.orly.dev/pkg/crypto/ec/bech32"
	"next.orly.dev/pkg/crypto/ec/schnorr"
	"next.orly.dev/pkg/crypto/sha256"
	"next.orly.dev/pkg/encoders/bech32encoding/pointers"
	"next.orly.dev/pkg/encoders/bech32encoding/tlv"
	"next.orly.dev/pkg/encoders/hex"
	"next.orly.dev/pkg/encoders/kind"
	"next.orly.dev/pkg/utils"
)

var (
	// NoteHRP is the Human Readable Prefix (HRP) for a nostr note (kind 1)
	NoteHRP = []byte("note")

	// NsecHRP is the Human Readable Prefix (HRP) for a nostr secret key
	NsecHRP = []byte("nsec")

	// NpubHRP is the Human Readable Prefix (HRP) for a nostr public key
	NpubHRP = []byte("npub")

	// NprofileHRP is the Human Readable Prefix (HRP) for a nostr profile metadata
	// event (kind 0)
	NprofileHRP = []byte("nprofile")

	// NeventHRP is the Human Readable Prefix (HRP) for a nostr event, which may
	// include relay hints to find the event, and the author's npub.
	NeventHRP = []byte("nevent")

	// NentityHRP is the Human Readable Prefix (HRP) for a nostr is a generic nostr
	// entity, which may include relay hints to find the event, and the author's
	// npub.
	NentityHRP = []byte("naddr")
)

// Decode a nostr bech32 encoded entity, return the prefix, and the decoded
// value, and any error if one occurred in the process of decoding.
func Decode(bech32string []byte) (prefix []byte, value any, err error) {
	var bits5 []byte
	if prefix, bits5, err = bech32.DecodeNoLimit(bech32string); chk.D(err) {
		return
	}
	var data []byte
	if data, err = bech32.ConvertBits(bits5, 5, 8, false); chk.D(err) {
		return prefix, nil, errorf.E(
			"failed translating data into 8 bits: %s", err.Error(),
		)
	}
	buf := bytes.NewBuffer(data)
	switch {
	case utils.FastEqual(prefix, NpubHRP) ||
		utils.FastEqual(prefix, NsecHRP) ||
		utils.FastEqual(prefix, NoteHRP):
		if len(data) < 32 {
			return prefix, nil, errorf.E(
				"data is less than 32 bytes (%d)", len(data),
			)
		}
		b := make([]byte, schnorr.PubKeyBytesLen*2)
		hex.EncBytes(b, data[:32])
		return prefix, b, nil
	case utils.FastEqual(prefix, NprofileHRP):
		var result pointers.Profile
		for {
			t, v := tlv.ReadEntry(buf)
			if len(v) == 0 {
				// end here
				if len(result.PublicKey) < 1 {
					return prefix, result, errorf.E("no pubkey found for nprofile")
				}
				return prefix, result, nil
			}
			switch t {
			case tlv.Default:
				if len(v) < 32 {
					return prefix, nil, errorf.E(
						"pubkey is less than 32 bytes (%d)", len(v),
					)
				}
				result.PublicKey = make([]byte, schnorr.PubKeyBytesLen*2)
				hex.EncBytes(result.PublicKey, v)
			case tlv.Relay:
				result.Relays = append(result.Relays, v)
			default:
				// ignore
			}
		}
	case utils.FastEqual(prefix, NeventHRP):
		var result pointers.Event
		for {
			t, v := tlv.ReadEntry(buf)
			if v == nil {
				// end here
				if len(result.ID) == 0 {
					return prefix, result, errorf.E("no id found for nevent")
				}
				return prefix, result, nil
			}
			switch t {
			case tlv.Default:
				if len(v) < 32 {
					return prefix, nil, errorf.E(
						"id is less than 32 bytes (%d)", len(v),
					)
				}
				result.ID = v
			case tlv.Relay:
				result.Relays = append(result.Relays, v)
			case tlv.Author:
				if len(v) < 32 {
					return prefix, nil, errorf.E(
						"author is less than 32 bytes (%d)", len(v),
					)
				}
				result.Author = make([]byte, schnorr.PubKeyBytesLen*2)
				hex.EncBytes(result.Author, v)
			case tlv.Kind:
				result.Kind = kind.New(binary.BigEndian.Uint32(v))
			default:
				// ignore
			}
		}
	case utils.FastEqual(prefix, NentityHRP):
		var result pointers.Entity
		for {
			t, v := tlv.ReadEntry(buf)
			if v == nil {
				// end here
				if result.Kind.ToU16() == 0 ||
					len(result.Identifier) < 1 ||
					len(result.PublicKey) < 1 {

					return prefix, result, errorf.E("incomplete naddr")
				}
				return prefix, result, nil
			}
			switch t {
			case tlv.Default:
				result.Identifier = v
			case tlv.Relay:
				result.Relays = append(result.Relays, v)
			case tlv.Author:
				if len(v) < 32 {
					return prefix, nil, errorf.E(
						"author is less than 32 bytes (%d)", len(v),
					)
				}
				result.PublicKey = make([]byte, schnorr.PubKeyBytesLen*2)
				hex.EncBytes(result.PublicKey, v)
			case tlv.Kind:
				result.Kind = kind.New(binary.BigEndian.Uint32(v))
			default:
				log.D.Ln("got a bogus TLV type code", t)
				// ignore
			}
		}
	}
	return prefix, data, errorf.E("unknown tag %s", prefix)
}

// EncodeNote encodes a standard nostr NIP-19 note entity (mostly meaning a
// nostr kind 1 short text note)
func EncodeNote(eventIDHex []byte) (s []byte, err error) {
	var b []byte
	if _, err = hex.DecBytes(b, eventIDHex); chk.D(err) {
		err = log.E.Err("failed to decode event id hex: %w", err)
		return
	}
	var bits5 []byte
	if bits5, err = bech32.ConvertBits(b, 8, 5, true); chk.D(err) {
		return
	}
	return bech32.Encode(NoteHRP, bits5)
}

// EncodeProfile encodes a pubkey and a set of relays into a bech32 encoded
// entity.
func EncodeProfile(publicKeyHex []byte, relays [][]byte) (s []byte, err error) {
	buf := &bytes.Buffer{}
	pb := make([]byte, schnorr.PubKeyBytesLen)
	if _, err = hex.DecBytes(pb, publicKeyHex); chk.D(err) {
		err = log.E.Err("invalid pubkey '%s': %w", publicKeyHex, err)
		return
	}
	tlv.WriteEntry(buf, tlv.Default, pb)
	for _, url := range relays {
		tlv.WriteEntry(buf, tlv.Relay, []byte(url))
	}
	var bits5 []byte
	if bits5, err = bech32.ConvertBits(buf.Bytes(), 8, 5, true); chk.D(err) {
		err = log.E.Err("failed to convert bits: %w", err)
		return
	}
	return bech32.Encode(NprofileHRP, bits5)
}

// EncodeEvent encodes an event, including relay hints and author pubkey.
func EncodeEvent(
	eventIDHex []byte, relays [][]byte, author []byte,
) (s []byte, err error) {
	buf := &bytes.Buffer{}
	id := make([]byte, sha256.Size)
	if _, err = hex.DecBytes(id, eventIDHex); chk.D(err) ||
		len(id) != 32 {
		return nil, errorf.E(
			"invalid id %d '%s': %v", len(id), eventIDHex,
			err,
		)
	}
	tlv.WriteEntry(buf, tlv.Default, id)
	for _, url := range relays {
		tlv.WriteEntry(buf, tlv.Relay, []byte(url))
	}
	pubkey := make([]byte, schnorr.PubKeyBytesLen)
	if _, err = hex.DecBytes(pubkey, author); len(pubkey) == 32 {
		tlv.WriteEntry(buf, tlv.Author, pubkey)
	}
	var bits5 []byte
	if bits5, err = bech32.ConvertBits(buf.Bytes(), 8, 5, true); chk.D(err) {
		err = log.E.Err("failed to convert bits: %w", err)
		return
	}
	return bech32.Encode(NeventHRP, bits5)
}

// EncodeEntity encodes a pubkey, kind, event ID, and relay hints.
func EncodeEntity(pk []byte, k *kind.K, id []byte, relays [][]byte) (
	s []byte, err error,
) {
	buf := &bytes.Buffer{}
	tlv.WriteEntry(buf, tlv.Default, []byte(id))
	for _, url := range relays {
		tlv.WriteEntry(buf, tlv.Relay, []byte(url))
	}
	pb := make([]byte, schnorr.PubKeyBytesLen)
	if _, err = hex.DecBytes(pb, pk); chk.D(err) {
		return nil, errorf.E("invalid pubkey '%s': %w", pb, err)
	}
	tlv.WriteEntry(buf, tlv.Author, pb)
	kindBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(kindBytes, uint32(k.K))
	tlv.WriteEntry(buf, tlv.Kind, kindBytes)
	var bits5 []byte
	if bits5, err = bech32.ConvertBits(buf.Bytes(), 8, 5, true); chk.D(err) {
		return nil, errorf.E("failed to convert bits: %w", err)
	}
	return bech32.Encode(NentityHRP, bits5)
}
