package loopdb

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"time"

	"github.com/lightninglabs/loop/labels"
	"github.com/lightningnetwork/lnd/routing/route"
	"go.etcd.io/bbolt"
)

// LoopInContract contains the data that is serialized to persistent storage for
// pending loop in swaps.
type LoopInContract struct {
	SwapContract

	// SweepConfTarget specifies the targeted confirmation target for the
	// client sweep tx.
	HtlcConfTarget int32

	// LastHop is the last hop to use for the loop in swap (optional).
	LastHop *route.Vertex

	// ExternalHtlc specifies whether the htlc is published by an external
	// source.
	ExternalHtlc bool
}

// LoopIn is a combination of the contract and the updates.
type LoopIn struct {
	Loop

	Contract *LoopInContract
}

// LastUpdateTime returns the last update time of this swap.
func (s *LoopIn) LastUpdateTime() time.Time {
	lastUpdate := s.LastUpdate()
	if lastUpdate == nil {
		return s.Contract.InitiationTime
	}

	return lastUpdate.Time
}

// serializeLoopInContract serialize the loop in contract into a byte slice.
func serializeLoopInContract(swap *LoopInContract) (
	[]byte, error) {

	var b bytes.Buffer

	if err := binary.Write(&b, byteOrder, swap.InitiationTime.UnixNano()); err != nil {
		return nil, err
	}

	if err := binary.Write(&b, byteOrder, swap.Preimage); err != nil {
		return nil, err
	}

	if err := binary.Write(&b, byteOrder, swap.AmountRequested); err != nil {
		return nil, err
	}

	n, err := b.Write(swap.HtlcKeys.SenderScriptKey[:])
	if err != nil {
		return nil, err
	}
	if n != keyLength {
		return nil, fmt.Errorf("sender key has invalid length")
	}

	n, err = b.Write(swap.HtlcKeys.ReceiverScriptKey[:])
	if err != nil {
		return nil, err
	}
	if n != keyLength {
		return nil, fmt.Errorf("receiver key has invalid length")
	}

	if err := binary.Write(&b, byteOrder, swap.CltvExpiry); err != nil {
		return nil, err
	}

	if err := binary.Write(&b, byteOrder, swap.MaxMinerFee); err != nil {
		return nil, err
	}

	if err := binary.Write(&b, byteOrder, swap.MaxSwapFee); err != nil {
		return nil, err
	}

	if err := binary.Write(&b, byteOrder, swap.InitiationHeight); err != nil {
		return nil, err
	}

	if err := binary.Write(&b, byteOrder, swap.HtlcConfTarget); err != nil {
		return nil, err
	}

	var lastHop route.Vertex
	if swap.LastHop != nil {
		lastHop = *swap.LastHop
	}
	if err := binary.Write(&b, byteOrder, lastHop[:]); err != nil {
		return nil, err
	}

	if err := binary.Write(&b, byteOrder, swap.ExternalHtlc); err != nil {
		return nil, err
	}

	return b.Bytes(), nil
}

// putLabel performs validation of a label and writes it to the bucket provided
// under the label key if it is non-zero.
func putLabel(bucket *bbolt.Bucket, label string) error {
	if len(label) == 0 {
		return nil
	}

	// Check that the label does not exceed our maximum length.
	if len(label) > labels.MaxLength {
		return labels.ErrLabelTooLong
	}

	return bucket.Put(labelKey, []byte(label))
}

// getLabel attempts to get an optional label stored under the label key in a
// bucket. If it is not present, an empty label is returned.
func getLabel(bucket *bbolt.Bucket) string {
	label := bucket.Get(labelKey)
	if label == nil {
		return ""
	}

	return string(label)
}

// deserializeLoopInContract deserializes the loop in contract from a byte slice.
func deserializeLoopInContract(value []byte) (*LoopInContract, error) {
	r := bytes.NewReader(value)

	contract := LoopInContract{}
	var err error
	var unixNano int64
	if err := binary.Read(r, byteOrder, &unixNano); err != nil {
		return nil, err
	}
	contract.InitiationTime = time.Unix(0, unixNano)

	if err := binary.Read(r, byteOrder, &contract.Preimage); err != nil {
		return nil, err
	}

	err = binary.Read(r, byteOrder, &contract.AmountRequested)
	if err != nil {
		return nil, err
	}

	n, err := r.Read(contract.HtlcKeys.SenderScriptKey[:])
	if err != nil {
		return nil, err
	}
	if n != keyLength {
		return nil, fmt.Errorf("sender key has invalid length")
	}

	n, err = r.Read(contract.HtlcKeys.ReceiverScriptKey[:])
	if err != nil {
		return nil, err
	}
	if n != keyLength {
		return nil, fmt.Errorf("receiver key has invalid length")
	}

	if err := binary.Read(r, byteOrder, &contract.CltvExpiry); err != nil {
		return nil, err
	}
	if err := binary.Read(r, byteOrder, &contract.MaxMinerFee); err != nil {
		return nil, err
	}

	if err := binary.Read(r, byteOrder, &contract.MaxSwapFee); err != nil {
		return nil, err
	}

	if err := binary.Read(r, byteOrder, &contract.InitiationHeight); err != nil {
		return nil, err
	}

	if err := binary.Read(r, byteOrder, &contract.HtlcConfTarget); err != nil {
		return nil, err
	}

	var lastHop route.Vertex
	if err := binary.Read(r, byteOrder, lastHop[:]); err != nil {
		return nil, err
	}
	var noLastHop route.Vertex
	if lastHop != noLastHop {
		contract.LastHop = &lastHop
	}

	if err := binary.Read(r, byteOrder, &contract.ExternalHtlc); err != nil {
		return nil, err
	}

	return &contract, nil
}
