package main

import (
	"bytes"
	"crypto/ed25519"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

const (
	baseURL   = "https://api.numus.online"
	partnerID = "partner-001"
)

func main() {
	privatePEM, err := os.ReadFile("numus-private.pem")
	must(err)
	block, _ := pem.Decode(privatePEM)
	if block == nil {
		panic("invalid private key PEM")
	}
	parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	must(err)
	privateKey, ok := parsed.(ed25519.PrivateKey)
	if !ok {
		panic("private key is not Ed25519")
	}

	method := http.MethodPut
	path := "/api/integration/endpoints"
	idempotencyKey := ""
	body, err := json.Marshal(map[string]string{
		"callbackUrl": "https://api.merchant.example/webhooks/numus",
		"returnUrl":   "https://merchant.example/payment/return",
	})
	must(err)

	target, err := url.Parse(path)
	must(err)
	timestamp := fmt.Sprintf("%d", time.Now().UTC().Unix())
	nonceBytes := make([]byte, 16)
	_, err = rand.Read(nonceBytes)
	must(err)
	nonce := hex.EncodeToString(nonceBytes)
	bodyHash := sha256.Sum256(body)
	canonical := strings.Join([]string{
		timestamp,
		nonce,
		method,
		target.EscapedPath(),
		target.Query().Encode(),
		idempotencyKey,
		hex.EncodeToString(bodyHash[:]),
	}, "\n")
	publicKey := openSSHPublicKey(privateKey.Public().(ed25519.PublicKey))
	authorization, err := json.Marshal(map[string]string{
		"partnerID": partnerID,
		"key":       publicKey,
		"sign":      base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, []byte(canonical))),
		"timestamp": timestamp,
		"nonce":     nonce,
	})
	must(err)

	request, err := http.NewRequest(method, baseURL+path, bytes.NewReader(body))
	must(err)
	request.Header.Set("Authorization", string(authorization))
	request.Header.Set("Content-Type", "application/json")
	response, err := (&http.Client{Timeout: 15 * time.Second}).Do(request)
	must(err)
	defer response.Body.Close()
	responseBody, err := io.ReadAll(io.LimitReader(response.Body, 1<<20))
	must(err)
	fmt.Printf("%d %s\n", response.StatusCode, responseBody)
}

func openSSHPublicKey(key ed25519.PublicKey) string {
	algorithm := []byte("ssh-ed25519")
	blob := appendSSHString(nil, algorithm)
	blob = appendSSHString(blob, key)
	return "ssh-ed25519 " + base64.StdEncoding.EncodeToString(blob)
}

func appendSSHString(target, value []byte) []byte {
	size := make([]byte, 4)
	binary.BigEndian.PutUint32(size, uint32(len(value)))
	target = append(target, size...)
	return append(target, value...)
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}
