package watchers

import (
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"regexp"

	"git.sp4ke.com/sp4ke/bit4sat/bus"
	"git.sp4ke.com/sp4ke/bit4sat/db"
	"git.sp4ke.com/sp4ke/bit4sat/ln"
	"git.sp4ke.com/sp4ke/bit4sat/lndrpc"
	"git.sp4ke.com/sp4ke/bit4sat/storage"
	lnrpc "github.com/lightningnetwork/lnd/lnrpc"
	"github.com/mediocregopher/radix/v3"
	"golang.org/x/net/context"
)

const (
	LastSettledInvoiceIndexKey = "last_invoice_settled_index_cursor"
	ReUploadInvoice            = `bit4sat upload`
	ReDownInvoice              = `bit4sat download`
)

// Last watched invoice
func getLastSettledInvoiceIndex() uint64 {
	var cursor uint64

	err := db.DB.Redis.Do(radix.FlatCmd(&cursor, "GET", LastSettledInvoiceIndexKey))
	if err != nil {
		return uint64(0)
	}

	return cursor
}

func setLastSettledInvoiceIndex(index uint64) {
	err := db.DB.Redis.Do(radix.FlatCmd(nil, "SET", LastSettledInvoiceIndexKey, index))
	if err != nil {
		panic(err)
	}
}

// Runs in a goroutine and keep swatching invoices
func WatchInvoice() {

	// Get the last invoice index cursor
	cursor := getLastSettledInvoiceIndex()

	log.Printf("watching settled from index %d", cursor)

	ctxb := context.Background()
	client, cleanUp := lndrpc.GetClient()
	defer cleanUp()

	subscription := &lnrpc.InvoiceSubscription{
		SettleIndex: cursor,
	}

	invoiceStream, err := client.SubscribeInvoices(ctxb, subscription)
	if err != nil {
		//return nil, err
	}

	for {
		invoice, err := invoiceStream.Recv()
		if err == io.EOF {
			break
		}

		if err != nil {
			log.Printf("error invoice stream %s", err)
			break
		}

		log.Printf("watcher, received invoice \n%s\n", invoice)

		// We are only interested in settled invoices
		if !invoice.Settled {
			continue
		}

		// Invoice was paid, we handle it
		handleSettledInvoice(invoice)
	}
}

func handleSettledInvoice(invoice *lnrpc.Invoice) {

	// we need to save it and also notify the pubsub channel

	// Save last settled index
	setLastSettledInvoiceIndex(invoice.SettleIndex)

	matchUp, err := regexp.MatchString(ReUploadInvoice, invoice.Memo)
	if err != nil {
		log.Printf("Error regex match: %s", err)
	}

	matchDown, err := regexp.MatchString(ReDownInvoice, invoice.Memo)
	if err != nil {
		log.Printf("Error regex match: %s", err)
	}

	// Handle upload related invoices
	if matchUp {

		// Update upload status to "paid"
		uploadId, err := storage.GetUploadIdForInvoice(hex.EncodeToString(invoice.RHash))
		log.Printf("watcher: found upload id %s for invoice %s", uploadId,
			hex.EncodeToString(invoice.RHash))
		if err != nil {
			log.Printf("error handleSettledInvoice: %s", err)
			return
		}

		err = storage.SetUploadStatus(uploadId, storage.UpPaid)
		if err != nil {
			log.Printf("error handleSettledInvoice: %s", err)
			return
		}

		// Get stored invoice for upload
		storedInvoice, err := storage.GetUploadInvoice(uploadId)
		if err != nil {
			log.Printf("error handleSettledInvoice: %s", err)
			return
		}
		//log.Printf("stored invoice %#v", storedInvoice)

		// Update stored invoice fields
		newInvoice := ln.UpdateInvoiceFromLnd(storedInvoice, invoice)

		//log.Printf("new invoice %#v", newInvoice)

		// Set invoice for upload
		err = storage.SetUploadInvoice(uploadId, newInvoice)
		if err != nil {
			log.Printf("error handleSettledInvoice: %s", err)
			return
		}

		log.Printf("Notifying invoice paid for upload %s", uploadId)
		// publish invoice was updated to upload_id_paid channel
		key := fmt.Sprintf("%s:%s", bus.InvoicePaidChannelPrefix,
			newInvoice.RHash)

		err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH",
			key, newInvoice))
	}

	// This is a download invoice
	if matchDown {

		// Store the full invoice
		key := fmt.Sprintf("invoice_%s", hex.EncodeToString(invoice.RHash))
		storedInvoice := ln.InvoiceFromLndIn(invoice)
		err = db.DB.Redis.Do(radix.FlatCmd(nil, "SET", key, storedInvoice))
		if err != nil {
			log.Printf("error handleSettledInvoice: %s", err)
			return
		}

		// Get upload_id related to this invoice
		key = fmt.Sprintf("invoice_id_%s_upload_id", storedInvoice.RHash)
		//log.Printf("looking for %s", key)
		var uploadId string
		err := db.DB.Redis.Do(radix.FlatCmd(&uploadId, "GET", key))
		if err != nil {
			log.Printf("error handleSettledInvoice: %s", err)
			return
		}

		// Add payment to the corresponding upload_id (user account)
		//log.Printf("updating payment for upload %s", uploadId)
		err = storage.AddPaymentToUpload(uploadId, storedInvoice.RHash,
			storedInvoice.Msatoshi)
		if err != nil {
			log.Printf("error handleSettledInvoice: %s", err)
			return
		}

		log.Printf("Notifying invoice paid for download")
		key = fmt.Sprintf("%s:%s", bus.InvoicePaidChannelPrefix,
			storedInvoice.RHash)

		err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH",
			key, storedInvoice))
	}

	if err != nil {
		log.Printf("error handleSettledInvoice: %s", err)
		return
	}

}
