//go:generate go run git.brut.systems/judah/rivit2html@latest docs/index.riv
//go:generate go run git.brut.systems/judah/rivit2html@latest docs/main.riv docs/main.go
package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
)

var (
	API_URL      string
	API_TOKEN    string
	REDIRECT_URL string
	PORT         string
)

func main() {
	var err error
	if API_URL, err = GetEnvironmentVariable("API_URL"); err != nil {
		log.Fatal(err)
	} else {
		if _, err := url.Parse(API_URL); err != nil {
			log.Fatalf("invalid API_URL %q", err)
		}

		if !strings.HasPrefix(API_URL, "http") {
			log.Fatalf("invalid API_URL %q - must being with http(s)://", API_URL)
		}
	}

	if REDIRECT_URL, err = GetEnvironmentVariable("REDIRECT_URL"); err != nil {
		log.Fatal(err)
	} else {
		if _, err := url.Parse(REDIRECT_URL); err != nil {
			log.Fatalf("invalid REDIRECT_URL %q", err)
		}

		if !strings.HasPrefix(REDIRECT_URL, "http") {
			log.Fatalf("invalid REDIRECT_URL %q - must begin with \"http(s)://\"", REDIRECT_URL)
		}
	}

	if token, err := GetEnvironmentVariable("API_TOKEN"); err != nil {
		log.Fatal(err)
	} else {
		API_TOKEN = "token " + token
	}

	if PORT, err = GetEnvironmentVariable("PORT"); err != nil {
		log.Fatal(err)
	} else {
		if _, err = strconv.ParseInt(PORT, 10, 64); err != nil {
			log.Fatalf("invalid port number %q", PORT)
		}
	}

	redirect := func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, REDIRECT_URL, http.StatusTemporaryRedirect)
	}

	sv := new(http.ServeMux)
	sv.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		redirect(w, r)
	})
	sv.HandleFunc("/{user}/{repo}/", func(w http.ResponseWriter, r *http.Request) {
		user := strings.TrimSpace(r.PathValue("user"))
		repo := strings.TrimSpace(r.PathValue("repo"))

		if len(user) == 0 || len(repo) == 0 {
			redirect(w, r)
			return
		}

		path := r.URL.Path
		path = strings.TrimSpace(path[strings.Index(path, repo)+len(repo):])
		path = strings.TrimPrefix(path, "/")

		if len(path) == 0 {
			path = "docs/index.html"
		}

		log.Printf("fetching file content %s:%s - %q", user, repo, path)

		content, err := FetchFile(user, repo, path)
		if err != nil {
			log.Println(err)
			redirect(w, r)
			return
		}

		if _, err := w.Write(content); err != nil {
			log.Println(err)
			redirect(w, r)
			return
		}
	})

	addr := fmt.Sprintf(":%s", PORT)
	log.Printf("Listening at %s", addr)

	if err := http.ListenAndServe(addr, sv); err != nil {
		log.Fatal(err)
	}
}

func FetchFile(user, repo, path string) ([]byte, error) {
	api_endpoint := FormatEndpoint("/repos/%s/%s/contents/%s", user, repo, url.PathEscape(path))
	body, status, err := ApiGet(api_endpoint)
	if err != nil {
		return nil, err
	}

	if status != http.StatusOK {
		return nil, fmt.Errorf("response had unexpected status %d - %s", status, string(body))
	}

	var response struct {
		Encoding string `json:"encoding"`
		Content  string `json:"content"`
	}

	err = json.Unmarshal(body, &response)
	if err != nil {
		return nil, err
	}

	if response.Encoding != "base64" {
		return nil, fmt.Errorf("expected base64 encoding, found %q", response.Encoding)
	}

	file, err := base64.StdEncoding.DecodeString(response.Content)
	if err != nil {
		return nil, err
	}

	return file, nil
}

func ApiGet(endpoint string) ([]byte, int, error) {
	req, err := http.NewRequest("GET", endpoint, nil)
	if err != nil {
		return nil, -1, err
	}

	req.Header.Add("accept", "application/json")
	req.Header.Add("Authorization", API_TOKEN)

	var client http.Client
	log.Printf("GET %q", endpoint)
	res, err := client.Do(req)
	if err != nil {
		return nil, -1, err
	}

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, -1, err
	}

	return body, res.StatusCode, nil
}

func GetEnvironmentVariable(name string) (string, error) {
	envvar := strings.TrimSpace(os.Getenv(name))
	if len(envvar) == 0 {
		return "", fmt.Errorf("required environment variable %q was not set", name)
	}
	return envvar, nil
}

func FormatEndpoint(endpoint string, args ...any) string {
	url, _ := url.JoinPath(API_URL, "api/v1", fmt.Sprintf(endpoint, args...))
	return url
}
