package view

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"local/dndex/config"
	"local/dndex/storage"
	"local/gziphttp"
	"log"
	"net/http"
	"strings"
	"time"

	"golang.org/x/time/rate"
)

var GitCommit string

func JSON(g storage.RateLimitedGraph) error {
	port := config.New().Port
	log.Println("listening on", port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", port), jsonHandler(g))
	return err
}

func jsonHandler(g storage.RateLimitedGraph) http.Handler {
	mux := http.NewServeMux()

	routes := []struct {
		path   string
		foo    func(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error
		noauth bool
	}{
		{
			path: "/port",
			foo:  port,
		},
		{
			path: "/who",
			foo:  who,
		},
		{
			path:   "/register",
			foo:    register,
			noauth: true,
		},
		{
			path: config.New().FilePrefix + "/",
			foo:  files,
		},
		{
			path:   "/version",
			foo:    version,
			noauth: true,
		},
	}

	for _, route := range routes {
		foo := route.foo
		auth := !route.noauth
		mux.HandleFunc(route.path, func(w http.ResponseWriter, r *http.Request) {
			if err := delay(w, r); err != nil {
				http.Error(w, err.Error(), 499)
			}

			if auth {
				if err := Auth(g, w, r); err != nil {
					return
				}
			}
			if err := foo(g, w, r); err != nil {
				status := http.StatusInternalServerError
				if strings.Contains(err.Error(), "collision") {
					status = http.StatusConflict
				}
				b, _ := json.Marshal(map[string]string{"error": err.Error()})
				http.Error(w, string(b), status)
			}
		})
	}

	return rateLimited(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if gziphttp.Can(r) {
			gz := gziphttp.New(w)
			defer gz.Close()
			w = gz
		}
		r.Body = struct {
			io.Reader
			io.Closer
		}{
			Reader: io.LimitReader(r.Body, config.New().MaxFileSize),
			Closer: r.Body,
		}
		mux.ServeHTTP(w, r)
	}))
}

func rateLimited(foo http.HandlerFunc) http.HandlerFunc {
	sysRPS := config.New().SysRPS
	limiter := rate.NewLimiter(rate.Limit(sysRPS), sysRPS)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := limiter.Wait(r.Context()); err != nil {
			http.Error(w, err.Error(), http.StatusTooManyRequests)
		}
		foo(w, r)
	})
}

func getAuthNamespace(r *http.Request) (string, error) {
	namespace, err := getNamespace(r)
	return strings.Join([]string{namespace, AuthKey}, "."), err
}

func getNamespace(r *http.Request) (string, error) {
	if strings.HasPrefix(r.URL.Path, config.New().FilePrefix) {
		path := strings.TrimPrefix(r.URL.Path, config.New().FilePrefix+"/")
		if path == r.URL.Path {
			return "", errors.New("no namespace on files")
		}
		path = strings.Split(path, "/")[0]
		if path == "" {
			return "", errors.New("empty namespace on files")
		}
		return path, nil
	}
	namespace := r.URL.Query().Get("namespace")
	if len(namespace) == 0 {
		return "", errors.New("no namespace found")
	}
	return namespace, nil
}

func version(_ storage.RateLimitedGraph, w http.ResponseWriter, _ *http.Request) error {
	enc := json.NewEncoder(w)
	enc.SetIndent("", "   ")
	return enc.Encode(map[string]string{"version": GitCommit})
}

func delay(w http.ResponseWriter, r *http.Request) error {
	select {
	case <-time.After(config.New().Delay):
	case <-r.Context().Done():
		return errors.New("client DCd")
	}
	return nil
}
