package main

import (
	"database/sql"
	"fmt"
	"math"
	"net/http"
	"strconv"
	"time"

	"github.com/labstack/echo/v4"
	"gopkg.in/guregu/null.v4"
)

type Market struct {
	Id          int
	Description string
	Active      bool
}

type Share struct {
	Id          string
	MarketId    int
	Description string
}

type Order struct {
	Session
	Share
	Market
	Invoice
	Id        string
	ShareId   string `form:"share_id"`
	Side      string `form:"side"`
	Price     int    `form:"price"`
	Quantity  int    `form:"quantity"`
	InvoiceId string
	CreatedAt time.Time
}

type Invoice struct {
	Session
	Id             string
	Msats          int
	ReceivedMsats  int
	Preimage       string
	PaymentRequest string
	PaymentHash    string
	CreatedAt      time.Time
	ExpiresAt      time.Time
	ConfirmedAt    null.Time
	HeldSince      null.Time
}

func costFunction(b float64, q1 float64, q2 float64) float64 {
	// reference: http://blog.oddhead.com/2006/10/30/implementing-hansons-market-maker/
	return b * math.Log(math.Pow(math.E, q1/b)+math.Pow(math.E, q2/b))
}

// logarithmic market scoring rule (LMSR) market maker from Robin Hanson:
// https://mason.gmu.edu/~rhanson/mktscore.pdf1
func BinaryLMSR(invariant int, funding int, q1 int, q2 int, dq1 int) float64 {
	b := float64(funding)
	fq1 := float64(q1)
	fq2 := float64(q2)
	fdq1 := float64(dq1)
	return costFunction(b, fq1+fdq1, fq2) - costFunction(b, fq1, fq2)
}

func order(c echo.Context) error {
	marketId := c.Param("id")
	// (TBD) Step 0: If SELL order, check share balance of user
	// (TBD) Step 1: respond with HODL invoice
	o := new(Order)
	if err := c.Bind(o); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "bad request")
	}
	session := c.Get("session").(Session)
	o.Pubkey = session.Pubkey
	amount := o.Quantity * o.Price
	invoice, err := lnd.CreateInvoice(session.Pubkey, amount*1000)
	if err != nil {
		return err
	}
	o.InvoiceId = invoice.Id
	if err := db.CreateOrder(o); err != nil {
		return err
	}
	qr, err := ToQR(invoice.PaymentRequest)
	if err != nil {
		return err
	}
	go lnd.CheckInvoice(invoice.PaymentHash)
	data := map[string]any{
		"session":     c.Get("session"),
		"ENV":         ENV,
		"lnurl":       invoice.PaymentRequest,
		"qr":          qr,
		"Invoice":     invoice,
		"RedirectURL": fmt.Sprintf("https://%s/market/%s", PUBLIC_URL, marketId),
	}
	return c.Render(http.StatusPaymentRequired, "invoice.html", data)
	// Step 2: After payment, confirm order if no matching order was found
	// if err := db.CreateOrder(o); err != nil {
	// 	if strings.Contains(err.Error(), "violates check constraint") {
	// 		return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
	// 	}
	// 	return err
	// }
	// (TBD) Step 3:
	//    Settle hodl invoice when matching order was found
	//    else cancel hodl invoice if expired
	// ...
	// return market(c)
}

func market(c echo.Context) error {
	marketId, err := strconv.ParseInt(c.Param("id"), 10, 64)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
	}

	var market Market
	if err = db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows {
		return echo.NewHTTPError(http.StatusNotFound, "Not Found")
	} else if err != nil {
		return err
	}

	var shares []Share
	if err = db.FetchShares(market.Id, &shares); err != nil {
		return err
	}
	var orders []Order
	if err = db.FetchOrders(&FetchOrdersWhere{MarketId: market.Id, Confirmed: true}, &orders); err != nil {
		return err
	}
	data := map[string]any{
		"session":     c.Get("session"),
		"ENV":         ENV,
		"Id":          market.Id,
		"Description": market.Description,
		// shares are sorted by description in descending order
		// that's how we know that YES must be the first share
		"YesShare": shares[0],
		"NoShare":  shares[1],
		"Orders":   orders,
	}
	return c.Render(http.StatusOK, "market.html", data)
}