// Package main is a simple implementation of a cURL like tool that can do
// simple GET/POST operations on a HTTP server that understands NIP-98
// authentication, with the signing key found in an environment variable.
package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"

	"lol.mleku.dev/chk"
	"lol.mleku.dev/log"

	"git.mleku.dev/mleku/nostr/encoders/bech32encoding"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/httpauth"
	"git.mleku.dev/mleku/nostr/interfaces/signer"
	"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"

	"next.orly.dev/pkg/version"
)

const secEnv = "NOSTR_SECRET_KEY"

var userAgent = fmt.Sprintf("nurl/%s", strings.TrimSpace(version.V))

func fail(format string, a ...any) {
	_, _ = fmt.Fprintf(os.Stderr, format+"\n", a...)
	os.Exit(1)
}

func main() {
	if len(os.Args) > 1 && os.Args[1] == "help" {
		fmt.Printf(
			`nurl help:

for nostr http using NIP-98 HTTP authentication:

    nurl <url> [file]

    if no file is given, the request will be processed as a HTTP GET.

    * NIP-98 secret will be expected in the environment variable "%s"
      - if absent, will not be added to the header.
      - endpoint is assumed to not require it if absent.
      - an error will be returned if it was needed.

    output will be rendered to stdout

`, secEnv,
		)
		os.Exit(0)
	}
	if len(os.Args) < 2 {
		fail(
			`error: nurl requires minimum 1 arg: <url>

    signing nsec (in bech32 format) is expected to be found in %s environment variable.

    use "help" to get usage information
`, secEnv,
		)
	}
	var err error
	var sign signer.I
	if sign, err = GetNIP98Signer(); err != nil {
		log.W.Ln("no signer available:", err)
	}
	var ur *url.URL
	if ur, err = url.Parse(os.Args[1]); chk.E(err) {
		fail("invalid URL: `%s` error: `%s`", os.Args[1], err.Error())
	}
	log.T.S(ur)
	if len(os.Args) == 2 {
		if err = Get(ur, sign); chk.E(err) {
			fail(err.Error())
		}
		return
	}
	if err = Post(os.Args[2], ur, sign); chk.E(err) {
		fail(err.Error())
	}
}

func GetNIP98Signer() (sign signer.I, err error) {
	nsec := os.Getenv(secEnv)
	var sk []byte
	if len(nsec) == 0 {
		err = fmt.Errorf("no bech32 secret key found in environment variable %s", secEnv)
		return
	} else if sk, err = bech32encoding.NsecToBytes([]byte(nsec)); chk.E(err) {
		err = fmt.Errorf("failed to decode nsec: '%s'", err.Error())
		return
	}
	var s *p8k.Signer
	if s, err = p8k.New(); chk.E(err) {
		err = fmt.Errorf("failed to create signer: '%s'", err.Error())
		return
	}
	if err = s.InitSec(sk); chk.E(err) {
		err = fmt.Errorf("failed to init signer: '%s'", err.Error())
		return
	}
	sign = s
	return
}

func Get(ur *url.URL, sign signer.I) (err error) {
	log.T.F("GET %s", ur.String())
	var r *http.Request
	if r, err = http.NewRequest("GET", ur.String(), nil); chk.E(err) {
		return
	}
	r.Header.Add("User-Agent", userAgent)
	if sign != nil {
		if err = httpauth.AddNIP98Header(
			r, ur, "GET", "", sign, 0,
		); chk.E(err) {
			fail(err.Error())
		}
	}
	client := &http.Client{
		CheckRedirect: func(
			req *http.Request,
			via []*http.Request,
		) error {
			return http.ErrUseLastResponse
		},
	}
	var res *http.Response
	if res, err = client.Do(r); chk.E(err) {
		err = fmt.Errorf("request failed: %w", err)
		return
	}
	if _, err = io.Copy(os.Stdout, res.Body); chk.E(err) {
		res.Body.Close()
		return
	}
	res.Body.Close()
	return
}

func Post(f string, ur *url.URL, sign signer.I) (err error) {
	log.T.F("POST %s", ur.String())
	var contentLength int64
	var payload io.ReadCloser
	// get the file path parameters and optional hash
	var fi os.FileInfo
	if fi, err = os.Stat(f); chk.E(err) {
		return
	}
	var b []byte
	if b, err = os.ReadFile(f); chk.E(err) {
		return
	}
	hb := sha256.Sum256(b)
	h := hex.Enc(hb[:])
	contentLength = fi.Size()
	if payload, err = os.Open(f); chk.E(err) {
		return
	}
	log.T.F("opened file %s hash %s", f, h)
	var r *http.Request
	r = &http.Request{
		Method:        "POST",
		URL:           ur,
		Proto:         "HTTP/1.1",
		ProtoMajor:    1,
		ProtoMinor:    1,
		Header:        make(http.Header),
		Body:          payload,
		ContentLength: contentLength,
		Host:          ur.Host,
	}
	r.Header.Add("User-Agent", userAgent)
	if sign != nil {
		if err = httpauth.AddNIP98Header(
			r, ur, "POST", string(h), sign, 0,
		); chk.E(err) {
			fail(err.Error())
		}
	}
	r.GetBody = func() (rc io.ReadCloser, err error) {
		rc = payload
		return
	}
	client := &http.Client{}
	var res *http.Response
	if res, err = client.Do(r); chk.E(err) {
		return
	}
	defer res.Body.Close()
	if _, err = io.Copy(os.Stdout, res.Body); chk.E(err) {
		return
	}
	return
}
