package api

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"path/filepath"
	"runtime/debug"

	jwtmiddleware "github.com/auth0/go-jwt-middleware"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
)

var (
	signingKey    = []byte("secret")
	store         = sessions.NewCookieStore([]byte("something-very-secret"))
	jwtMiddleware = jwtmiddleware.New(jwtmiddleware.Options{
		ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
			return signingKey, nil
		},
		SigningMethod: jwt.SigningMethodHS256,
		Extractor:     fromCookie,
		ErrorHandler:  onError,
	})
)

type User struct {
	Name  string
	Admin bool
}

func Handlers() *mux.Router {
	r := mux.NewRouter()

	// Authentication

	r.Handle("/login", loginHandler())
	r.Handle("/logout", logoutHandler())

	// Dashboard

	r.Handle("/", jwtMiddleware.Handler(recoverHandler(dashboardHandler())))

	// School

	r.Handle("/school/{id}", jwtMiddleware.Handler(recoverHandler(schoolShowHandler())))
	r.Handle("/school/{id}/update", jwtMiddleware.Handler(recoverHandler(schoolUpdateHandler()))).Methods("GET", "POST")

	// Teachers

	r.Handle("/teachers/check_username/{username}", jwtMiddleware.Handler(recoverHandler(teachersCheckUsernameHandler())))
	r.Handle("/teachers", jwtMiddleware.Handler(recoverHandler(teachersHandler())))
	r.Handle("/teachers/add", jwtMiddleware.Handler(recoverHandler(teachersAddHandler()))).Methods("GET", "POST")
	r.Handle("/teachers/{id}/delete", jwtMiddleware.Handler(recoverHandler(teachersDeleteHandler()))).Methods("DELETE")
	r.Handle("/teachers/{id}", jwtMiddleware.Handler(recoverHandler(teacherShowHandler()))).Methods("GET")
	r.Handle("/teachers/{id}/update", jwtMiddleware.Handler(recoverHandler(teacherUpdateHandler()))).Methods("GET", "POST")
	r.Handle("/teachers/import", jwtMiddleware.Handler(recoverHandler(teachersImportHandler()))).Methods("POST")

	// Teachers -> Subjects

	r.Handle("/teachers/{id}/subjects/add", jwtMiddleware.Handler(recoverHandler(teacherSubjectsAdd()))).Methods("GET")
	r.Handle("/teachers/{id}/subjects/add", jwtMiddleware.Handler(recoverHandler(teacherSubjectsAdd()))).Methods("POST")
	r.Handle("/teachers/{id}/subjects/{subject_id}/delete", jwtMiddleware.Handler(recoverHandler(teachersSubjectsDelete()))).Methods("DELETE")

	// Teachers -> Classes

	r.Handle("/teachers/{id}/classes/add", jwtMiddleware.Handler(recoverHandler(teacherClassesAdd()))).Methods("GET")
	r.Handle("/teachers/{id}/classes/add", jwtMiddleware.Handler(recoverHandler(teacherClassesAdd()))).Methods("POST")
	r.Handle("/teachers/{id}/classes/{class_id}/delete", jwtMiddleware.Handler(recoverHandler(teachersClassesDelete()))).Methods("DELETE")

	// Subjects

	r.Handle("/subjects", jwtMiddleware.Handler(recoverHandler(subjectsHandler())))
	r.Handle("/subjects/add", jwtMiddleware.Handler(recoverHandler(subjectsAddHandler()))).Methods("GET", "POST")
	r.Handle("/subjects/{id}", jwtMiddleware.Handler(recoverHandler(subjectsShowHandler()))).Methods("GET")
	r.Handle("/subjects/{id}/update", jwtMiddleware.Handler(recoverHandler(subjectsUpdateHandler()))).Methods("GET", "POST")
	r.Handle("/subjects/{id}/delete", jwtMiddleware.Handler(recoverHandler(subjectsDeleteHandler()))).Methods("DELETE")
	r.Handle("/subjects/import", jwtMiddleware.Handler(recoverHandler(subjectsImportHandler()))).Methods("POST")

	// Activities

	r.Handle("/activities", jwtMiddleware.Handler(recoverHandler(activitiesHandler())))
	r.Handle("/activities/add", jwtMiddleware.Handler(recoverHandler(activitiesAddHandler()))).Methods("GET", "POST")
	r.Handle("/activities/{id}/update", jwtMiddleware.Handler(recoverHandler(activitiesUpdateHandler()))).Methods("GET", "POST")
	r.Handle("/activities/{id}/delete", jwtMiddleware.Handler(recoverHandler(activitiesDeleteHandler()))).Methods("DELETE")

	// Classes

	r.Handle("/classes", jwtMiddleware.Handler(recoverHandler(classesHandler())))
	r.Handle("/classes/add", jwtMiddleware.Handler(recoverHandler(classesAddHandler()))).Methods("GET", "POST")
	r.Handle("/classes/{id}", jwtMiddleware.Handler(recoverHandler(classesShowHandler()))).Methods("GET")
	r.Handle("/classes/{id}/update", jwtMiddleware.Handler(recoverHandler(classesUpdateHandler()))).Methods("GET", "POST")
	r.Handle("/classes/{id}/delete", jwtMiddleware.Handler(recoverHandler(classesDeleteHandler()))).Methods("DELETE")
	r.Handle("/classes/import", jwtMiddleware.Handler(recoverHandler(classesImportHandler()))).Methods("POST")

	// Static file server

	r.PathPrefix("/").Handler(http.FileServer(http.Dir("./dist/")))

	return r
}

func onError(w http.ResponseWriter, r *http.Request, err string) {
	http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
}

func respondWithStaticFile(w http.ResponseWriter, filename string) error {
	f, err := ioutil.ReadFile(filepath.Join("public/html", filename))
	if err != nil {
		return err
	}
	w.Write(f)
	return nil
}

func fromCookie(r *http.Request) (string, error) {
	session, err := store.Get(r, "login-session")
	if err != nil {
		return "", nil
	}

	if session.Values["token"] == nil {
		return "", nil
	}

	token := session.Values["token"].([]uint8)

	return string(token), nil
}

func recoverHandler(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				panicMsg := fmt.Sprintf("PANIC: %v\n\n== STACKTRACE ==\n%s", err, debug.Stack())
				log.Print(panicMsg)
				http.Error(w, panicMsg, http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	}
	return http.HandlerFunc(fn)
}
