package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"

	"tildegit.org/tjp/sliderule"
	"tildegit.org/tjp/sliderule/gemini"
	"tildegit.org/tjp/sliderule/spartan"
)

const usage = `Resource fetcher for the small web.

Usage:
  sw-fetch (-h | --help)
  sw-fetch [-v | --verbose] [-o PATH | --output PATH] [-k | --keyfile PATH] [ -c | --certfile PATH ] [ -s | --skip-verify ] [ -u | --upload ] URL

Options:
  -h --help           Show this screen.
  -v --verbose        Display more diagnostic information on standard error.
  -o --output PATH    Send the fetched resource to PATH instead of standard out.
  -k --keyfile PATH   Path to the TLS key file to use.
  -c --certfile PATH  Path to the TLS certificate file to use.
  -s --skip-verify    Don't verify server TLS certificates.
  -u --upload         Use stdin as the request body on supported protocols and don't follow redirects.
`

func main() {
	conf := configure()
	cl := sliderule.NewClient(conf.clientTLS)

	var response *sliderule.Response
	var err error

	if conf.upload {
		response, err = cl.Upload(conf.url.String(), os.Stdin)
	} else {
		response, err = cl.Fetch(conf.url.String())
	}
	if err != nil {
		fail(err.Error() + "\n")
	}
	defer func() {
		_ = response.Close()
		_ = conf.output.Close()
	}()

	success := printResponse(response, conf)
	if !success {
		os.Exit(1)
	}
}

type config struct {
	verbose   bool
	upload    bool
	output    io.WriteCloser
	url       *url.URL
	clientTLS *tls.Config
}

func configure() config {
	if len(os.Args) == 1 {
		fail(usage)
	}

	conf := config{output: os.Stdout}
	key := ""
	cert := ""
	verify := true

	for i := 1; i <= len(os.Args)-1; i += 1 {
		switch os.Args[i] {
		case "-h", "--help":
			os.Stdout.WriteString(usage)
			os.Exit(0)
		case "-v", "--verbose":
			conf.verbose = true
		case "-o", "--output":
			if i+1 == len(os.Args)-1 {
				fail(usage)
			}

			out := os.Args[i+1]
			if out != "-" {
				output, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
				if err != nil {
					fmt.Println(err.Error())
					failf("'%s' is not a valid path\n", out)
				}
				conf.output = output
			}

			i += 1
		case "-k", "--keyfile":
			if i+1 == len(os.Args)-1 {
				fail(usage)
			}

			i += 1
			key = os.Args[i]
		case "-c", "--certfile":
			if i+1 == len(os.Args)-1 {
				fail(usage)
			}

			i += 1
			cert = os.Args[i]
		case "-s", "--skip-verify":
			verify = false
		case "-u", "--upload":
			conf.upload = true
		}
	}

	conf.clientTLS = &tls.Config{}
	if key != "" || cert != "" {
		if key == "" || cert == "" {
			fail("-k|--keyfile and -c|--certfile must both be present, or neither\n")
		}
		tlsConf, err := gemini.FileTLS(cert, key)
		if err != nil {
			failf("failed to load TLS key pair: %s", err.Error())
		}
		conf.clientTLS = tlsConf
	}
	conf.clientTLS.InsecureSkipVerify = !verify

	u, err := url.Parse(os.Args[len(os.Args)-1])
	if err != nil || u.Scheme == "" {
		fail(usage)
	}
	conf.url = u

	return conf
}

func fail(msg string) {
	os.Stderr.WriteString(msg)
	os.Exit(1)
}

func failf(msg string, args ...any) {
	fmt.Fprintf(os.Stderr, msg, args...)
	os.Exit(1)
}

func printResponse(response *sliderule.Response, conf config) bool {
	success := true

	switch conf.url.Scheme {
	case "http", "https":
		switch int(response.Status) / 100 {
		case 4, 5:
			fmt.Fprintf(os.Stderr, "http %d: %s\n", response.Status, http.StatusText(int(response.Status)))
			success = false
		}
	case "gemini": //, "titan"
		switch gemini.ResponseCategoryForStatus(response.Status) {
		case gemini.ResponseCategoryTemporaryFailure, gemini.ResponseCategoryPermanentFailure, gemini.ResponseCategoryCertificateRequired:
			fmt.Fprintf(os.Stderr, "gemini %d: %s\n", response.Status, response.Meta.(string))
			success = false
		}
	case "spartan":
		switch response.Status {
		case spartan.StatusClientError, spartan.StatusServerError:
			fmt.Fprintf(os.Stderr, "spartan %d: %s\n", response.Status, response.Meta.(string))
			success = false
		}
	}

	if _, err := io.Copy(conf.output, response.Body); err != nil {
		fail(err.Error())
	}

	return success
}