package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

type handlerFunc func(w http.ResponseWriter, r *http.Request) error

func errorTo502(f handlerFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := f(w, r); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	})
}

// shiftPath splits off the first component of p, which will be cleaned of
// relative components before processing. head will never contain a slash and
// tail will always be a rooted path without trailing slash.
func shiftPath(p string) (head, tail string) {
	p = path.Clean("/" + p)
	i := strings.Index(p[1:], "/") + 1
	if i <= 0 {
		return p[1:], "/"
	}
	return p[1:i], p[i:]
}

func listHandler(w http.ResponseWriter, r *http.Request) error {
	// TODO: list all packages from /var/cache/pk4/completions.src.txt
	fmt.Fprintf(w, "ohai!")
	return nil
}

// lsrHandler returns a recursive listing of filenames of the specified source
// package in the specified version.
//
// It is typically called by the JavaScript editor when opening a source
// package.
func lsrHandler(w http.ResponseWriter, r *http.Request) error {
	srcpackage, tail := shiftPath(r.URL.Path)
	srcversion, _ := shiftPath(tail)
	if srcpackage == "" || srcversion == "" {
		return fmt.Errorf("syntax: /ls-R/<srcpackage>/<srcversion>")
	}
	// TODO: ensure the package is in the cache beforehand
	var paths []string
	dir := filepath.Join("/home/michael/.cache/pk4/", srcpackage+"-"+srcversion) + "/"
	if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info != nil && info.IsDir() &&
			(filepath.Base(path) == ".git" ||
				filepath.Base(path) == ".pc") {
			return filepath.SkipDir
		}
		if rel := strings.TrimPrefix(path, dir); rel != "" {
			paths = append(paths, rel)
		}
		return nil
	}); err != nil {
		return err
	}
	sort.Strings(paths)
	if err := json.NewEncoder(w).Encode(paths); err != nil {
		return err
	}
	log.Printf("pkg %q, version %q", srcpackage, srcversion)
	return nil
}

// origHandler serves original, unmodified source package files from the pk4
// cache.
//
// TODO: use snapshot.d.n instead?
func origHandler(w http.ResponseWriter, r *http.Request) error {
	srcpackage, tail1 := shiftPath(r.URL.Path)
	srcversion, tail := shiftPath(tail1)
	if srcpackage == "" || srcversion == "" {
		return fmt.Errorf("syntax: /orig/<srcpackage>/<srcversion>/<path>")
	}
	log.Printf("pkg %q, version %q, path %q", srcpackage, srcversion, tail)
	dir := filepath.Join("/home/michael/.cache/pk4/", srcpackage+"-"+srcversion) + "/"
	http.ServeFile(w, r, filepath.Join(dir, tail))
	return nil
}

func main() {
	http.Handle("/", http.FileServer(http.Dir(".")))
	http.HandleFunc("/edit/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "edith.html")
	})
	http.Handle("/list", errorTo502(listHandler))
	// TODO: middleware to set cache header to \infty
	http.Handle("/ls-R/", http.StripPrefix("/ls-R/", errorTo502(lsrHandler)))
	http.Handle("/orig/", http.StripPrefix("/orig/", errorTo502(origHandler)))
	log.Fatal(http.ListenAndServe(":3391", nil))
}

/* TODO:
envisioned contributor workflow:
1. future contributor creates a guest account (pending state)
2. current DD vouches for that guest account
3. guest account can now be used for authN via sso.d.o
4. authZ is service-specific. e.g., user can create new gitlab repos under their guest-acc space, but not write to any debian ones (yet)
*/
