package contractcourt

import (
	"bytes"
	"io"

	"github.com/lightningnetwork/lnd/tlv"
)

const (
	commitCtrlBlockType       tlv.Type = 0
	revokeCtrlBlockType       tlv.Type = 1
	outgoingHtlcCtrlBlockType tlv.Type = 2
	incomingHtlcCtrlBlockType tlv.Type = 3
	secondLevelCtrlBlockType  tlv.Type = 4

	anchorTapTweakType                tlv.Type = 0
	htlcTweakCtrlBlockType            tlv.Type = 1
	secondLevelHtlcTweakCtrlBlockType tlv.Type = 2
)

// taprootBriefcase is a supplemental storage struct that contains all the
// information we need to sweep taproot outputs.
type taprootBriefcase struct {
	// CtrlBlock is the set of control block for the taproot outputs.
	CtrlBlocks tlv.RecordT[tlv.TlvType0, ctrlBlocks]

	// TapTweaks is the set of taproot tweaks for the taproot outputs that
	// are to be spent via a keyspend path. This includes anchors, and any
	// revocation paths.
	TapTweaks tlv.RecordT[tlv.TlvType1, tapTweaks]

	// SettledCommitBlob is an optional record that contains an opaque blob
	// that may be used to properly sweep commitment outputs on a force
	// close transaction.
	SettledCommitBlob tlv.OptionalRecordT[tlv.TlvType2, tlv.Blob]

	// BreachCommitBlob is an optional record that contains an opaque blob
	// used to sweep a remote party's breached output.
	BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob]

	// TODO(roasbeef): htlc blobs
}

// TODO(roasbeef): morph into new tlv record

// newTaprootBriefcase returns a new instance of the taproot specific briefcase
// variant.
func newTaprootBriefcase() *taprootBriefcase {
	return &taprootBriefcase{
		CtrlBlocks: tlv.NewRecordT[tlv.TlvType0](newCtrlBlocks()),
		TapTweaks:  tlv.NewRecordT[tlv.TlvType1](newTapTweaks()),
	}
}

// EncodeRecords returns a slice of TLV records that should be encoded.
func (t *taprootBriefcase) EncodeRecords() []tlv.Record {
	records := []tlv.Record{
		t.CtrlBlocks.Record(),
		t.TapTweaks.Record(),
	}

	t.SettledCommitBlob.WhenSome(
		func(r tlv.RecordT[tlv.TlvType2, tlv.Blob]) {
			records = append(records, r.Record())
		},
	)
	t.BreachedCommitBlob.WhenSome(
		func(r tlv.RecordT[tlv.TlvType3, tlv.Blob]) {
			records = append(records, r.Record())
		},
	)

	return records
}

// DecodeRecords returns a slice of TLV records that should be decoded.
func (t *taprootBriefcase) DecodeRecords() []tlv.Record {
	return []tlv.Record{
		t.CtrlBlocks.Record(),
		t.TapTweaks.Record(),
	}
}

// Encode records returns a slice of TLV records that should be encoded.
func (t *taprootBriefcase) Encode(w io.Writer) error {
	stream, err := tlv.NewStream(t.EncodeRecords()...)
	if err != nil {
		return err
	}

	return stream.Encode(w)
}

// Decode decodes the given reader into the target struct.
func (t *taprootBriefcase) Decode(r io.Reader) error {
	settledCommitBlob := t.SettledCommitBlob.Zero()
	breachedCommitBlob := t.BreachedCommitBlob.Zero()
	records := append(
		t.DecodeRecords(),
		settledCommitBlob.Record(),
		breachedCommitBlob.Record(),
	)
	stream, err := tlv.NewStream(records...)
	if err != nil {
		return err
	}

	typeMap, err := stream.DecodeWithParsedTypes(r)
	if err != nil {
		return err
	}

	if val, ok := typeMap[t.SettledCommitBlob.TlvType()]; ok && val == nil {
		t.SettledCommitBlob = tlv.SomeRecordT(settledCommitBlob)
	}
	if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil {
		t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob)
	}

	return nil
}

// resolverCtrlBlocks is a map of resolver IDs to their corresponding control
// block.
type resolverCtrlBlocks map[resolverID][]byte

// newResolverCtrlBlockss returns a new instance of the resolverCtrlBlocks.
func newResolverCtrlBlocks() resolverCtrlBlocks {
	return make(resolverCtrlBlocks)
}

// recordSize returns the size of the record in bytes.
func (r *resolverCtrlBlocks) recordSize() uint64 {
	// Each record will be serialized as: <num_total_records> || <record>,
	// where <record> is serialized as: <resolver_key> || <length> ||
	// <ctrl_block>.
	numBlocks := uint64(len(*r))
	baseSize := tlv.VarIntSize(numBlocks)

	recordSize := baseSize
	for _, ctrlBlock := range *r {
		recordSize += resolverIDLen
		recordSize += tlv.VarIntSize(uint64(len(ctrlBlock)))
		recordSize += uint64(len(ctrlBlock))
	}

	return recordSize
}

// Encode encodes the control blocks into the target writer.
func (r *resolverCtrlBlocks) Encode(w io.Writer) error {
	numBlocks := uint64(len(*r))

	var buf [8]byte
	if err := tlv.WriteVarInt(w, numBlocks, &buf); err != nil {
		return err
	}

	for id, ctrlBlock := range *r {
		ctrlBlock := ctrlBlock

		if _, err := w.Write(id[:]); err != nil {
			return err
		}

		if err := varBytesEncoder(w, &ctrlBlock, &buf); err != nil {
			return err
		}
	}

	return nil
}

// Decode decodes the given reader into the target struct.
func (r *resolverCtrlBlocks) Decode(reader io.Reader) error {
	var buf [8]byte

	numBlocks, err := tlv.ReadVarInt(reader, &buf)
	if err != nil {
		return err
	}

	for i := uint64(0); i < numBlocks; i++ {
		var id resolverID
		if _, err := io.ReadFull(reader, id[:]); err != nil {
			return err
		}

		var ctrlBlock []byte
		err := varBytesDecoder(reader, &ctrlBlock, &buf, 0)
		if err != nil {
			return err
		}

		(*r)[id] = ctrlBlock
	}

	return nil
}

// resolverCtrlBlocksEncoder is a custom TLV encoder for the
// resolverCtrlBlocks.
func resolverCtrlBlocksEncoder(w io.Writer, val any, _ *[8]byte) error {
	if typ, ok := val.(*resolverCtrlBlocks); ok {
		return (*typ).Encode(w)
	}

	return tlv.NewTypeForEncodingErr(val, "resolverCtrlBlocks")
}

// rsolverCtrlBlocksDecoder is a custom TLV decoder for the resolverCtrlBlocks.
func resolverCtrlBlocksDecoder(r io.Reader, val any, _ *[8]byte,
	l uint64) error {

	if typ, ok := val.(*resolverCtrlBlocks); ok {
		blockReader := io.LimitReader(r, int64(l))

		resolverBlocks := newResolverCtrlBlocks()
		err := resolverBlocks.Decode(blockReader)
		if err != nil {
			return err
		}

		*typ = resolverBlocks

		return nil
	}

	return tlv.NewTypeForDecodingErr(val, "resolverCtrlBlocks", l, l)
}

// ctrlBlocks is the set of control blocks we need to sweep all the output for
// a taproot/musig2 channel.
type ctrlBlocks struct {
	// CommitSweepCtrlBlock is the serialized control block needed to sweep
	// our commitment output.
	CommitSweepCtrlBlock []byte

	// RevokeSweepCtrlBlock is the serialized control block that's used to
	// sweep the reovked output of a breaching party.
	RevokeSweepCtrlBlock []byte

	// OutgoingHtlcCtrlBlocks is the set of serialized control blocks for
	// all outgoing HTLCs. This is the set of HTLCs that we offered to the
	// remote party. Depending on which commitment transaction was
	// broadcast, we'll either sweep here and be done, or also need to go
	// to the second level.
	OutgoingHtlcCtrlBlocks resolverCtrlBlocks

	// IncomingHtlcCtrlBlocks is the set of serialized control blocks for
	// all incoming HTLCs
	IncomingHtlcCtrlBlocks resolverCtrlBlocks

	// SecondLevelCtrlBlocks is the set of serialized control blocks for
	// need to sweep the second level HTLCs on our commitment transaction.
	SecondLevelCtrlBlocks resolverCtrlBlocks
}

// newCtrlBlocks returns a new instance of the ctrlBlocks struct.
func newCtrlBlocks() ctrlBlocks {
	return ctrlBlocks{
		OutgoingHtlcCtrlBlocks: newResolverCtrlBlocks(),
		IncomingHtlcCtrlBlocks: newResolverCtrlBlocks(),
		SecondLevelCtrlBlocks:  newResolverCtrlBlocks(),
	}
}

// varBytesEncoder is a custom TLV encoder for a variable length byte slice.
func varBytesEncoder(w io.Writer, val any, buf *[8]byte) error {
	if t, ok := val.(*[]byte); ok {
		if err := tlv.WriteVarInt(w, uint64(len(*t)), buf); err != nil {
			return err
		}

		return tlv.EVarBytes(w, t, buf)
	}

	return tlv.NewTypeForEncodingErr(val, "[]byte")
}

// varBytesDecoder is a custom TLV decoder for a variable length byte slice.
func varBytesDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
	if typ, ok := val.(*[]byte); ok {
		bytesLen, err := tlv.ReadVarInt(r, buf)
		if err != nil {
			return err
		}

		var bytes []byte
		if err := tlv.DVarBytes(r, &bytes, buf, bytesLen); err != nil {
			return err
		}

		*typ = bytes

		return nil
	}

	return tlv.NewTypeForDecodingErr(val, "[]byte", l, l)
}

// ctrlBlockEncoder is a custom TLV encoder for the ctrlBlocks struct.
func ctrlBlockEncoder(w io.Writer, val any, _ *[8]byte) error {
	if t, ok := val.(*ctrlBlocks); ok {
		return (*t).Encode(w)
	}

	return tlv.NewTypeForEncodingErr(val, "ctrlBlocks")
}

// ctrlBlockDecoder is a custom TLV decoder for the ctrlBlocks struct.
func ctrlBlockDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error {
	if typ, ok := val.(*ctrlBlocks); ok {
		ctrlReader := io.LimitReader(r, int64(l))

		var ctrlBlocks ctrlBlocks
		err := ctrlBlocks.Decode(ctrlReader)
		if err != nil {
			return err
		}

		*typ = ctrlBlocks

		return nil
	}

	return tlv.NewTypeForDecodingErr(val, "ctrlBlocks", l, l)
}

// EncodeRecords returns the set of TLV records that encode the control block
// for the commitment transaction.
func (c *ctrlBlocks) EncodeRecords() []tlv.Record {
	var records []tlv.Record

	if len(c.CommitSweepCtrlBlock) > 0 {
		records = append(records, tlv.MakePrimitiveRecord(
			commitCtrlBlockType, &c.CommitSweepCtrlBlock,
		))
	}

	if len(c.RevokeSweepCtrlBlock) > 0 {
		records = append(records, tlv.MakePrimitiveRecord(
			revokeCtrlBlockType, &c.RevokeSweepCtrlBlock,
		))
	}

	if c.OutgoingHtlcCtrlBlocks != nil {
		records = append(records, tlv.MakeDynamicRecord(
			outgoingHtlcCtrlBlockType, &c.OutgoingHtlcCtrlBlocks,
			c.OutgoingHtlcCtrlBlocks.recordSize,
			resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
		))
	}

	if c.IncomingHtlcCtrlBlocks != nil {
		records = append(records, tlv.MakeDynamicRecord(
			incomingHtlcCtrlBlockType, &c.IncomingHtlcCtrlBlocks,
			c.IncomingHtlcCtrlBlocks.recordSize,
			resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
		))
	}

	if c.SecondLevelCtrlBlocks != nil {
		records = append(records, tlv.MakeDynamicRecord(
			secondLevelCtrlBlockType, &c.SecondLevelCtrlBlocks,
			c.SecondLevelCtrlBlocks.recordSize,
			resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
		))
	}

	return records
}

// DecodeRecords returns the set of TLV records that decode the control block.
func (c *ctrlBlocks) DecodeRecords() []tlv.Record {
	return []tlv.Record{
		tlv.MakePrimitiveRecord(
			commitCtrlBlockType, &c.CommitSweepCtrlBlock,
		),
		tlv.MakePrimitiveRecord(
			revokeCtrlBlockType, &c.RevokeSweepCtrlBlock,
		),
		tlv.MakeDynamicRecord(
			outgoingHtlcCtrlBlockType, &c.OutgoingHtlcCtrlBlocks,
			c.OutgoingHtlcCtrlBlocks.recordSize,
			resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
		),
		tlv.MakeDynamicRecord(
			incomingHtlcCtrlBlockType, &c.IncomingHtlcCtrlBlocks,
			c.IncomingHtlcCtrlBlocks.recordSize,
			resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
		),
		tlv.MakeDynamicRecord(
			secondLevelCtrlBlockType, &c.SecondLevelCtrlBlocks,
			c.SecondLevelCtrlBlocks.recordSize,
			resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
		),
	}
}

// Record returns a TLV record that can be used to encode/decode the control
// blocks.  type from a given TLV stream.
func (c *ctrlBlocks) Record() tlv.Record {
	recordSize := func() uint64 {
		var (
			b   bytes.Buffer
			buf [8]byte
		)
		if err := ctrlBlockEncoder(&b, c, &buf); err != nil {
			panic(err)
		}

		return uint64(len(b.Bytes()))
	}

	return tlv.MakeDynamicRecord(
		0, c, recordSize, ctrlBlockEncoder, ctrlBlockDecoder,
	)
}

// Encode encodes the set of control blocks.
func (c *ctrlBlocks) Encode(w io.Writer) error {
	stream, err := tlv.NewStream(c.EncodeRecords()...)
	if err != nil {
		return err
	}

	return stream.Encode(w)
}

// Decode decodes the set of control blocks.
func (c *ctrlBlocks) Decode(r io.Reader) error {
	stream, err := tlv.NewStream(c.DecodeRecords()...)
	if err != nil {
		return err
	}

	return stream.Decode(r)
}

// htlcTapTweakss maps an outpoint (the same format as the resolver ID) to the
// tap tweak needed to sweep a breached HTLC output. This is used for both the
// first and second level HTLC outputs.
type htlcTapTweaks map[resolverID][32]byte

// newHtlcTapTweaks returns a new instance of the htlcTapTweaks struct.
func newHtlcTapTweaks() htlcTapTweaks {
	return make(htlcTapTweaks)
}

// recordSize returns the size of the record in bytes.
func (h *htlcTapTweaks) recordSize() uint64 {
	// Each record will be serialized as: <num_tweaks> || <tweak>, where
	// <tweak> is serialized as: <resolver_key> || <tweak>.
	numTweaks := uint64(len(*h))
	baseSize := tlv.VarIntSize(numTweaks)

	recordSize := baseSize
	for range *h {
		// Each tweak is a fixed 32 bytes, so we just tally that an the
		// size of the resolver ID.
		recordSize += resolverIDLen
		recordSize += 32
	}

	return recordSize
}

// Encode encodes the tap tweaks into the target writer.
func (h *htlcTapTweaks) Encode(w io.Writer) error {
	numTweaks := uint64(len(*h))

	var buf [8]byte
	if err := tlv.WriteVarInt(w, numTweaks, &buf); err != nil {
		return err
	}

	for id, tweak := range *h {
		tweak := tweak

		if _, err := w.Write(id[:]); err != nil {
			return err
		}

		if _, err := w.Write(tweak[:]); err != nil {
			return err
		}
	}

	return nil
}

// htlcTapTweaksEncoder is a custom TLV encoder for the htlcTapTweaks struct.
func htlcTapTweaksEncoder(w io.Writer, val any, _ *[8]byte) error {
	if t, ok := val.(*htlcTapTweaks); ok {
		return (*t).Encode(w)
	}

	return tlv.NewTypeForEncodingErr(val, "htlcTapTweaks")
}

// htlcTapTweaksDecoder is a custom TLV decoder for the htlcTapTweaks struct.
func htlcTapTweaksDecoder(r io.Reader, val any, _ *[8]byte,
	l uint64) error {

	if typ, ok := val.(*htlcTapTweaks); ok {
		tweakReader := io.LimitReader(r, int64(l))

		htlcTweaks := newHtlcTapTweaks()
		err := htlcTweaks.Decode(tweakReader)
		if err != nil {
			return err
		}

		*typ = htlcTweaks

		return nil
	}

	return tlv.NewTypeForDecodingErr(val, "htlcTapTweaks", l, l)
}

// Decode decodes the tap tweaks into the target struct.
func (h *htlcTapTweaks) Decode(reader io.Reader) error {
	var buf [8]byte

	numTweaks, err := tlv.ReadVarInt(reader, &buf)
	if err != nil {
		return err
	}

	for i := uint64(0); i < numTweaks; i++ {
		var id resolverID
		if _, err := io.ReadFull(reader, id[:]); err != nil {
			return err
		}

		var tweak [32]byte
		if _, err := io.ReadFull(reader, tweak[:]); err != nil {
			return err
		}

		(*h)[id] = tweak
	}

	return nil
}

// tapTweaks stores the set of taptweaks needed to perform keyspends for the
// commitment outputs.
type tapTweaks struct {
	// AnchorTweak is the tweak used to derive the key used to spend the
	// anchor output.
	AnchorTweak []byte

	// BreachedHtlcTweaks stores the set of tweaks needed to sweep the
	// revoked first level output of an HTLC.
	BreachedHtlcTweaks htlcTapTweaks

	// BreachedSecondLevelHtlcTweaks stores the set of tweaks needed to
	// sweep the revoked *second* level output of an HTLC.
	BreachedSecondLevelHltcTweaks htlcTapTweaks
}

// newTapTweaks returns a new tapTweaks struct.
func newTapTweaks() tapTweaks {
	return tapTweaks{
		BreachedHtlcTweaks:            make(htlcTapTweaks),
		BreachedSecondLevelHltcTweaks: make(htlcTapTweaks),
	}
}

// tapTweaksEncoder is a custom TLV encoder for the tapTweaks struct.
func tapTweaksEncoder(w io.Writer, val any, _ *[8]byte) error {
	if t, ok := val.(*tapTweaks); ok {
		return (*t).Encode(w)
	}

	return tlv.NewTypeForEncodingErr(val, "tapTweaks")
}

// tapTweaksDecoder is a custom TLV decoder for the tapTweaks struct.
func tapTweaksDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error {
	if typ, ok := val.(*tapTweaks); ok {
		tweakReader := io.LimitReader(r, int64(l))

		var tapTweaks tapTweaks
		err := tapTweaks.Decode(tweakReader)
		if err != nil {
			return err
		}

		*typ = tapTweaks

		return nil
	}

	return tlv.NewTypeForDecodingErr(val, "tapTweaks", l, l)
}

// EncodeRecords returns the set of TLV records that encode the tweaks.
func (t *tapTweaks) EncodeRecords() []tlv.Record {
	var records []tlv.Record

	if len(t.AnchorTweak) > 0 {
		records = append(records, tlv.MakePrimitiveRecord(
			anchorTapTweakType, &t.AnchorTweak,
		))
	}

	if len(t.BreachedHtlcTweaks) > 0 {
		records = append(records, tlv.MakeDynamicRecord(
			htlcTweakCtrlBlockType, &t.BreachedHtlcTweaks,
			t.BreachedHtlcTweaks.recordSize,
			htlcTapTweaksEncoder, htlcTapTweaksDecoder,
		))
	}

	if len(t.BreachedSecondLevelHltcTweaks) > 0 {
		records = append(records, tlv.MakeDynamicRecord(
			secondLevelHtlcTweakCtrlBlockType,
			&t.BreachedSecondLevelHltcTweaks,
			t.BreachedSecondLevelHltcTweaks.recordSize,
			htlcTapTweaksEncoder, htlcTapTweaksDecoder,
		))
	}

	return records
}

// DecodeRecords returns the set of TLV records that decode the tweaks.
func (t *tapTweaks) DecodeRecords() []tlv.Record {
	return []tlv.Record{
		tlv.MakePrimitiveRecord(anchorTapTweakType, &t.AnchorTweak),
		tlv.MakeDynamicRecord(
			htlcTweakCtrlBlockType, &t.BreachedHtlcTweaks,
			t.BreachedHtlcTweaks.recordSize,
			htlcTapTweaksEncoder, htlcTapTweaksDecoder,
		),
		tlv.MakeDynamicRecord(
			secondLevelHtlcTweakCtrlBlockType,
			&t.BreachedSecondLevelHltcTweaks,
			t.BreachedSecondLevelHltcTweaks.recordSize,
			htlcTapTweaksEncoder, htlcTapTweaksDecoder,
		),
	}
}

// Record returns a TLV record that can be used to encode/decode the tap
// tweaks.
func (t *tapTweaks) Record() tlv.Record {
	recordSize := func() uint64 {
		var (
			b   bytes.Buffer
			buf [8]byte
		)
		if err := tapTweaksEncoder(&b, t, &buf); err != nil {
			panic(err)
		}

		return uint64(len(b.Bytes()))
	}

	return tlv.MakeDynamicRecord(
		0, t, recordSize, tapTweaksEncoder, tapTweaksDecoder,
	)
}

// Encode encodes the set of tap tweaks.
func (t *tapTweaks) Encode(w io.Writer) error {
	stream, err := tlv.NewStream(t.EncodeRecords()...)
	if err != nil {
		return err
	}

	return stream.Encode(w)
}

// Decode decodes the set of tap tweaks.
func (t *tapTweaks) Decode(r io.Reader) error {
	stream, err := tlv.NewStream(t.DecodeRecords()...)
	if err != nil {
		return err
	}

	return stream.Decode(r)
}
