package pretix

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"maunium.net/go/mautrix/event"
)

const (
	eventOrderPlaced	= "pretix.event.order.placed"

	urlIndividualOrder	= "%s/api/v1/organizers/%s/events/%s/orders/%s/"
)

func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) {
	if r.Header.Get("Content-Type") != "application/json" {
		writeStatus(w, http.StatusBadRequest)
		return
	}

	data := struct{
		Action		string
		Code		string
		Event		string
		Organizer	string
	}{}
	decoder := json.NewDecoder(r.Body)

	err := decoder.Decode(&data)
	if err != nil {
		writeStatus(w, http.StatusBadRequest)
		return
	}

	if data.Action != eventOrderPlaced {
		writeStatus(w, http.StatusBadRequest)
		return
	}

	req, err := http.NewRequest(http.MethodGet, s.buildURL(urlIndividualOrder, data.Organizer, data.Event, data.Code), nil)
	if err != nil {
		writeStatus(w, http.StatusInternalServerError)
		return
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", fmt.Sprint("Token ", s.config.APIToken))

	resp, err := s.client.Do(req)
	if err != nil {
		writeStatus(w, http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		writeStatus(w, http.StatusInternalServerError)
		return
	}

	if resp.Header.Get("Content-Type") != "application/json" {
		writeStatus(w, http.StatusInternalServerError)
		return
	}

	decoder = json.NewDecoder(resp.Body)
	order := struct{
		Code		string
		Datetime	time.Time
		Positions	[]struct{
			Price	string
		}
		Total		string
	}{}

	err = decoder.Decode(&order)
	if err != nil {
		writeStatus(w, http.StatusInternalServerError)
		return
	}

	result, err := s.db.Exec("INSERT INTO orders VALUES(?);", order.Code)
	if err != nil {
		writeStatus(w, http.StatusInternalServerError)
		return
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		writeStatus(w, http.StatusInternalServerError)
		return
	}

	if rowsAffected > 0 && order.Datetime.Add(72 * time.Hour).After(time.Now()) { // pretix retries failed webhooks for up to three days
		numFree := 0
		numPaid := 0
		for _, position := range order.Positions {
			euros, cents, err := parsePrice(position.Price)
			if err != nil {
				continue
			}

			if euros > 0 || cents > 0 {
				numPaid++
			} else {
				numFree++
			}
		}

		var verb, free, conjunction, paid, noun, total string
		if numFree+numPaid == 1 {
			verb = "wurde "
		} else {
			verb = "wurden "
		}
		if numFree == 1 {
			free = "ein kostenloses "
		} else if numFree > 1 {
			free = fmt.Sprint(numFree, " kostenlose ")
		}
		if numPaid == 1 {
			paid = "ein bezahltes "
		} else if numPaid > 1 {
			paid = fmt.Sprint(numPaid, " bezahlte ")
		}
		if numFree > 0 && numPaid > 0 {
			conjunction = "und "
		}
		if numPaid == 1 || numPaid == 0 && numFree == 1 { // match number of adjective immediately before noun
			noun = "Ticket "
		} else {
			noun = "Tickets "
		}
		if totalEuros, totalCents, err := parsePrice(order.Total); err == nil && (totalEuros > 0 || totalCents > 0) {
			var adverb, fraction string
			if numPaid > 1 {
				adverb = "insgesamt "
			}
			if totalCents > 0 {
				fraction = fmt.Sprintf(",%02d", totalCents)
			}
			total = fmt.Sprint("für ", adverb, totalEuros, fraction, " Euro ")
		}

		message := event.MessageEventContent{
			MsgType:	event.MsgText,
			Body:		fmt.Sprint("\U0001f389 Es ", verb, free, conjunction, paid, noun, total, "bestellt."),
		}

		success := s.matrix.Broadcast(&message)
		if !success {
			writeStatus(w, http.StatusInternalServerError)
		}
	}
}

func parsePrice(price string) (euros uint64, cents uint64, err error) {
	parts := strings.Split(price, ".")

	euros, err = strconv.ParseUint(parts[0], 10, 64)
	if err != nil {
		return 0, 0, err
	}

	if len(parts) > 1 {
		for len(parts[1]) < 2 {
			parts[1] += "0"
		}
		parts[1] = parts[1][:2]

		parts[1] = strings.TrimLeft(parts[1], "0")
		if parts[1] == "" {
			parts[1] = "0"
		}

		cents, err = strconv.ParseUint(parts[1], 10, 64)
		if err != nil {
			return 0, 0, err
		}
	}

	return euros, cents, nil
}
