package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

func run() error {
	op := flag.String("operation", "status", "status,prefix,reserve-prefix,manifest,submit,trial,trial-receipt,platform-receipt,reset")
	file := flag.String("file", "", "JSON body file")
	event := flag.String("event-id", "", "receipt event ID")
	flag.Parse()
	base, token, id := os.Getenv("SDK_PORTAL_URL"), os.Getenv("SDK_PORTAL_TOKEN"), os.Getenv("SDK_APPLICATION_ID")
	if !regexp.MustCompile(`^[a-z][a-z0-9_-]{0,63}$`).MatchString(os.Getenv("APPLICATION_CATEGORY")) {
		return errors.New("application category required")
	}
	u, e := url.Parse(base)
	n, ne := strconv.Atoi(id)
	if e != nil || u.Host == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || strings.Trim(u.Path, "/") != "" || ne != nil || n < 1 || len(token) != 64 {
		return errors.New("invalid App portal environment")
	}
	if u.Scheme != "https" && !(u.Scheme == "http" && (u.Hostname() == "127.0.0.1" || u.Hostname() == "localhost")) {
		return errors.New("portal HTTPS required")
	}
	method, suffix := "GET", ""
	switch *op {
	case "status":
	case "prefix":
		suffix = "/user-id-prefix"
	case "reserve-prefix":
		method = "PUT"
		suffix = "/user-id-prefix"
	case "manifest":
		suffix = "/manifest"
	case "submit":
		method = "PUT"
		suffix = "/submission"
	case "trial":
		method = "POST"
		suffix = "/trials"
	case "reset":
		method = "DELETE"
		suffix = "/trials"
	case "trial-receipt", "platform-receipt":
		if *event == "" || len(*event) > 200 {
			return errors.New("event ID required")
		}
		suffix = "/trials/" + url.PathEscape(*event)
		if *op == "platform-receipt" {
			suffix = "/receipts/" + url.PathEscape(*event)
		}
	default:
		return errors.New("unknown operation")
	}
	var b []byte
	if method != "GET" {
		if *file == "" {
			return errors.New("body file required")
		}
		b, e = os.ReadFile(*file)
		if e != nil {
			return e
		}
		if !json.Valid(b) || len(b) > 65536 {
			return errors.New("invalid or oversized JSON")
		}
	}
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()
	r, e := http.NewRequestWithContext(ctx, method, strings.TrimRight(base, "/")+"/api/sdk/applications/"+url.PathEscape(os.Getenv("APPLICATION_CATEGORY"))+"/"+strconv.Itoa(n)+suffix, bytes.NewReader(b))
	if e != nil {
		return e
	}
	r.Header.Set("Authorization", "Bearer "+token)
	r.Header.Set("Content-Type", "application/json")
	client := http.Client{Timeout: 15 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
	res, e := client.Do(r)
	if e != nil {
		return errors.New("App portal network failure")
	}
	defer res.Body.Close()
	b, e = io.ReadAll(io.LimitReader(res.Body, 2<<20+1))
	if e != nil || len(b) > 2<<20 || !json.Valid(b) {
		return errors.New("invalid portal response")
	}
	if res.StatusCode != 200 {
		return fmt.Errorf("App portal HTTP %d; query state and feedback", res.StatusCode)
	}
	_, e = os.Stdout.Write(append(b, '\n'))
	return e
}
func main() {
	if e := run(); e != nil {
		fmt.Fprintln(os.Stderr, e)
		os.Exit(1)
	}
}
