package ln

import (
	"encoding/hex"
	"encoding/json"
	"log"
	"strconv"
	"strings"
	"time"

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

type Currency int

const (
	CurMSat Currency = iota
	CurSat
	CurBTC
	CurUSD
	CurEur
)

const (
	Paid int = iota
	UnPaid
	Expired
)

var InvoiceStatus = map[int]string{
	UnPaid:  "unpaid",
	Paid:    "paid",
	Expired: "expired",
}

const (
	InfoEndpoint    = "info"
	InvoiceEndpoint = "invoice"
)

var (
	CurrencyString = map[Currency]string{
		CurUSD:  "USD",
		CurSat:  "SAT",
		CurBTC:  "BTC",
		CurMSat: "MSAT",
		CurEur:  "EUR",
	}

	CurrencyID = map[string]Currency{
		"USD":  CurUSD,
		"SAT":  CurSat,
		"MSAT": CurMSat,
		"BTC":  CurBTC,
		"EUR":  CurEur,
	}
)

type timestamp time.Time

func (t *timestamp) UnmarshalJSON(in []byte) error {

	if string(in) == "null" {
		*t = timestamp(time.Unix(0, 0))
		return nil
	}

	// Try to first parse in RFC3339
	var val time.Time
	var err error

	// put the input in string mode
	strIn := string(in)

	// clean
	strIn = strings.Replace(strIn, "\"", "", -1)

	if val, err = time.Parse(time.RFC3339, strIn); err != nil {

		val, err := strconv.Atoi(strIn)
		if err != nil {
			log.Printf("error unmarshal time %s", in)
			return err
		}

		parsedTime := time.Unix(int64(val), 0)
		*t = timestamp(parsedTime)
		return nil

	}

	*t = timestamp(val)

	return nil
}

func (t timestamp) MarshalJSON() ([]byte, error) {
	str := strconv.Itoa(int(time.Time(t).Unix()))
	return []byte(str), nil
}

func (t timestamp) String() string {
	return time.Time(t).Format(time.RFC3339)
}

type Invoice struct {
	AddIndex       uint64    `json:"-"`
	SettleIndex    uint64    `json:"-"`
	Description    string    `json:"description"`
	Msatoshi       float64   `json:"msatoshi"`
	Payreq         string    `json:"payreq"`
	RHash          string    `json:"rhash"`
	PreImage       string    `json:"-"` // used as secret admin token
	Status         string    `json:"status"`
	Settled        bool      `json:"settled"`
	PaidAt         timestamp `json:"paid_at"`
	CreatedAt      timestamp `json:"created_at"`
	ExpiresAt      timestamp `json:"expires_at"`
	Expired        bool      `json:"-"`
	QuotedCurrency string    `json:"-"`
	QuotedAmount   float64   `json:"-"`
}

func (i Invoice) MarshalBinary() ([]byte, error) {
	return json.Marshal(i)
}

func (i *Invoice) UnmarshalBinary(b []byte) error {
	return json.Unmarshal(b, &i)
}

// Create new Invoice from lnrpc.Invoice
func InvoiceFromLndIn(lndInvoice *lnrpc.Invoice) *Invoice {

	//log.Printf("new invoice form lnd with value %d", lndInvoice.Value)
	//log.Printf("setting msat to %f", float64(lndInvoice.Value*1000))
	invoice := Invoice{
		AddIndex:    lndInvoice.AddIndex,
		Description: lndInvoice.GetMemo(),
		Msatoshi:    float64(lndInvoice.Value * 1000),
		Payreq:      lndInvoice.PaymentRequest,
		RHash:       hex.EncodeToString(lndInvoice.RHash),
		PreImage:    hex.EncodeToString(lndInvoice.RPreimage),
		CreatedAt:   timestamp(time.Unix(lndInvoice.CreationDate, 0)),
		PaidAt:      timestamp(time.Unix(lndInvoice.SettleDate, 0)),
		Status:      InvoiceStatus[UnPaid],
	}

	invoice.ExpiresAt = timestamp(time.Time(invoice.CreatedAt).Add(time.Second * time.Duration(lndInvoice.Expiry)))

	// Calculate status
	if lndInvoice.Settled {
		invoice.Settled = true
		invoice.Status = InvoiceStatus[Paid]
	}

	if time.Now().After(time.Time(invoice.ExpiresAt)) {
		invoice.Status = InvoiceStatus[Expired]
		invoice.Expired = true
	}

	return &invoice
}

// Update stored invoice from lnd invoice
func UpdateInvoiceFromLnd(storedIn *Invoice, newIn *lnrpc.Invoice) *Invoice {
	storedIn.SettleIndex = newIn.SettleIndex
	storedIn.PaidAt = timestamp(time.Unix(newIn.SettleDate, 0))

	// Calculate status
	if newIn.Settled {
		storedIn.Status = InvoiceStatus[Paid]
		storedIn.Settled = true
	}

	// Handle expired status
	if time.Now().After(time.Time(storedIn.ExpiresAt)) {
		storedIn.Status = InvoiceStatus[Expired]
		storedIn.Expired = true
	}

	return storedIn
}

func IsExpiredInvoice(creation, expiry int64) bool {
	createdAt := time.Unix(int64(creation), 0)
	expiresAt := createdAt.Add(time.Second * time.Duration(expiry))

	return time.Now().After(expiresAt)

}
