package main

import (
	"context"
	_ "embed"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"path"
	"strings"
	"syscall"
	"time"
)

type Context struct {
	DB   DB
	User IDU
}

func HTTP(port int, db DB) error {
	ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
	defer can()

	foo := func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			httpGUI(w, r)
		} else if strings.HasPrefix(r.URL.Path, "/api/questions") && strings.HasSuffix(r.URL.Path, "/answers") && r.Method == http.MethodPost {
			httpPostQuestionAnswers(w, r)
		} else {
			http.NotFound(w, r)
		}
	}
	foo = withAuth(foo)
	foo = withDB(foo, db)
	foo = withCtx(foo, ctx)

	go func() {
		http.ListenAndServe(fmt.Sprintf(":%d", port), http.HandlerFunc(foo))
	}()
	<-ctx.Done()
	return nil
}

func extract(ctx context.Context) Context {
	v := ctx.Value("__context")
	v2, _ := v.(Context)
	return v2
}

func inject(ctx context.Context, v Context) context.Context {
	return context.WithValue(ctx, "__context", v)
}

func withCtx(foo http.HandlerFunc, ctx context.Context) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		r = r.WithContext(ctx)
		foo(w, r)
	}
}

func withDB(foo http.HandlerFunc, db DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		c := extract(r.Context())
		c.DB = db
		r = r.WithContext(inject(r.Context(), c))
		foo(w, r)
	}
}

func withAuth(foo http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		u, _, ok := r.BasicAuth()
		if !ok || u == "" {
			w.Header().Set("WWW-Authenticate", "Basic")
			w.WriteHeader(401)
			return
		}
		c := extract(r.Context())
		c.User = IDU(u)
		r = r.WithContext(inject(r.Context(), c))
		foo(w, r)
	}
}

//go:embed public/root.html
var httpGUIHTML string

func httpGUI(w http.ResponseWriter, r *http.Request) {
	body := httpGUIHTML
	if os.Getenv("DEBUG") != "" {
		b, _ := os.ReadFile("public/root.html")
		body = string(b)
	}
	ctx := extract(r.Context())
	body = strings.ReplaceAll(body, "{{USER}}", string(ctx.User))
	assignments, err := httpAssignments(r.Context())
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	assignmentsB, _ := json.Marshal(assignments)
	body = strings.ReplaceAll(body, "{{ASSIGNMENTS_JSON}}", string(assignmentsB))
	w.Write([]byte(body))
}

func httpAssignments(ctx context.Context) (interface{}, error) {
	db := extract(ctx).DB
	user := extract(ctx).User
	todo := map[IDQ]Question{}
	for q, _ := range db.HistoryOf(user) {
		if time.Until(db.Next(user, q)) > 0 {
			continue
		}
		todo[q] = db.Question(q)
	}
	return todo, nil
}

func httpPostQuestionAnswers(w http.ResponseWriter, r *http.Request) {
	idq := IDQ(path.Base(strings.Split(r.URL.Path, "/answers")[0]))
	var payload struct {
		Answer string `json:"answer"`
		Passed bool   `json:"passed"`
	}
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	ctx := extract(r.Context())
	if err := ctx.DB.PushAnswer(ctx.User, idq, Renderable(payload.Answer), payload.Passed); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}
