package main

import (
	"context"
	"embed"
	"flag"
	"net/http"
	"os"
	"time"

	"github.com/jmoiron/sqlx"
	log "github.com/sirupsen/logrus"
	"modernc.org/ql"
	_ "modernc.org/ql/driver"

	"git.c3pb.de/gbe/invinoveritas/auth"
	"git.c3pb.de/gbe/invinoveritas/session"
)

//go:embed templates/*.tpl
var templateFS embed.FS

//go:embed static/*
var staticFS embed.FS

func httpError(w http.ResponseWriter, msg string, err error, status int) {
	if err != nil {
		msg += ": " + err.Error()
	}

	log.WithField("status", status).
		WithError(err).
		Error(msg)

	http.Error(w, msg, status)
}

type sessionProvider interface {
	ListSessions(context.Context) ([]session.Info, error)
	DeleteSession(ctx context.Context, token string) error
}

type userProvider interface {
	ListUsers(context.Context) ([]string, error)
	CreateUser(ctx context.Context, name string) (string, error)
	DeleteUser(ctx context.Context, name string) error

	UpdatePassword(ctx context.Context, name string, oldPW, newPW string) error
}

type Handler struct {
	sqlDB *sqlx.DB
	sp    sessionProvider
	up    userProvider
}

func logRequest(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		defer func() {
			d := time.Since(start)

			remote := r.Header.Get("X-Forwarded-For")
			if remote == "" {
				remote = r.RemoteAddr
			}

			log.WithFields(log.Fields{
				"method":   r.Method,
				"url":      r.URL,
				"remote":   r.RemoteAddr,
				"duration": d,
				"auth":     auth.Get(r),
			}).Info("handling request")
		}()

		next.ServeHTTP(w, r)
	})
}

func addCacheHeaders(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Cache-Control", "public, max-age=86400, immutable")
		next.ServeHTTP(w, r)
	})
}

func openQLdb(path string) (*sqlx.DB, error) {
	var retried bool

	path += ".ql"

retry:
	db, err := sqlx.Open("ql2", path)
	if err != nil {
		return nil, err
	}

	log.Info("running test-select on db")
	_, err = db.Exec(`SELECT count(*) FROM __Table`)
	if err != nil && !retried {
		log.Println("got err:", err)
		retried = true

		// WAL may be corrupted. Manually remove it and re-try open. See
		// https://gitlab.com/cznic/ql/-/issues/227 for more info.
		err := os.Remove(ql.WalName(path))
		if err != nil {
			return nil, err
		}

		goto retry
	}

	if err != nil {
		return nil, err
	}

	return db, nil
}

func main() {
	formatter := log.TextFormatter{
		DisableColors: true,
	}

	log.StandardLogger().Formatter = &formatter

	dbPath := flag.String("db", "vino", "Path to database file")
	listenAddr := flag.String("listen", "127.0.0.1:7878", "Listening address")
	debug := flag.Bool("debug", false, "Enable debug logging")

	flag.Parse()

	if *debug {
		log.SetLevel(log.DebugLevel)
	}

	db, err := openQLdb(*dbPath)
	if err != nil {
		log.WithError(err).Fatalln("can't open DB:")
	}
	defer db.Close()

	err = initDB(context.TODO(), db)
	if err != nil {
		log.WithField("wal_name", ql.WalName(*dbPath+".ql")).
			WithError(err).
			Fatalln("can't initalize DB")
	}

	http.HandleFunc("/favicon.ico", http.NotFound)

	http.Handle("/static/", logRequest(addCacheHeaders(http.FileServer(http.FS(staticFS)))))

	sessions := session.Provider{
		DB: db,
	}

	handler := Handler{
		sqlDB: db,
		sp:    sessions,
		up:    sessions,
	}

	http.Handle("/details/img/", auth.Require(logRequest(addCacheHeaders(handler.img())), sessions.Handler(templateFS), sessions))
	http.Handle("/details/", auth.Require(logRequest(handler.details()), sessions.Handler(templateFS), sessions))
	http.Handle("/user/", auth.Require(logRequest(handler.user()), sessions.Handler(templateFS), sessions))
	http.Handle("/", auth.Require(logRequest(handler.index()), sessions.Handler(templateFS), sessions))

	log.Printf("here we go, listening on http://%s", *listenAddr)

	err = http.ListenAndServe(*listenAddr, nil)
	if err != nil {
		log.WithError(err).Fatalln("http handler failed")
	}
}