package gitserver

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

func hooksHandler(config *Config) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		segments := strings.Split(r.URL.Path[1:], "/")
		for _, s := range segments {
			if len(s) == 0 || s == "." || s == ".." {
				http.NotFound(w, r)
				return
			}
		}
		if !config.AllowPush {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}

		switch len(segments) {
		case 2:
			path := filepath.Join(config.Home, segments[0], "hooks", segments[1])
			if segments[0] == "hooks" {
				path = filepath.Join(config.HookDirectory, segments[1])
			}

			switch r.Method {
			// TODO: support HEAD or prevent GET for security
			case "GET":
				w.Header().Set("Content-Type", "text/plain")
				http.ServeFile(w, r, path)

			case "DELETE":
				if err := os.Remove(path); err != nil {
					log.Printf("error: attempted to remove %s: %v", path, err)
				}
				w.WriteHeader(http.StatusNoContent)

			case "PUT":
				if stat, err := os.Stat(path); err == nil {
					if stat.IsDir() || (stat.Mode()&0111) == 0 {
						http.Error(w, fmt.Errorf("only executable hooks can be changed: %v", stat).Error(), http.StatusInternalServerError)
						return
					}
					// unsymlink and overwrite
					if (stat.Mode() & os.ModeSymlink) != 0 {
						os.Remove(path)
					}
				}
				f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0750)
				if err != nil {
					http.Error(w, fmt.Errorf("unable to open hook file: %v", err).Error(), http.StatusInternalServerError)
					return
				}
				defer f.Close()
				max := config.MaxHookBytes + 1
				body := io.LimitReader(r.Body, max)
				buf := make([]byte, max)
				n, err := io.ReadFull(body, buf)
				if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
					http.Error(w, fmt.Errorf("unable to read hook: %v", err).Error(), http.StatusInternalServerError)
					return
				}
				if int64(n) == max {
					http.Error(w, fmt.Errorf("hook was too long, truncated to %d bytes", config.MaxHookBytes).Error(), 422)
				} else {
					w.WriteHeader(http.StatusOK)
				}
				if _, err := f.Write(buf[:n]); err != nil {
					http.Error(w, fmt.Errorf("unable to write hook: %v", err).Error(), http.StatusInternalServerError)
					return
				}

			default:
				http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			}

		default:
			http.NotFound(w, r)
		}
	})
}