package main

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

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

	"git.c3pb.de/gbe/invinoveritas/auth"
	"git.c3pb.de/gbe/invinoveritas/log"
	"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, r *http.Request, msg string, err error, status int) {
	log := log.Get(r)

	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 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
	}

	logrus.Info("running test-select on db")
	_, err = db.Exec(`SELECT count(*) FROM __Table`)
	if err != nil && !retried {
		logrus.WithError(err).
			Error("got error")
		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 := logrus.TextFormatter{
		DisableColors: true,
	}

	logrus.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 {
		logrus.SetLevel(logrus.DebugLevel)
	}

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

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

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

	http.Handle("/static/", log.Request(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(log.Request(addCacheHeaders(handler.img())), sessions.Handler(templateFS), sessions))
	http.Handle("/details/", auth.Require(log.Request(handler.details()), sessions.Handler(templateFS), sessions))
	http.Handle("/user/", auth.Require(log.Request(handler.user()), sessions.Handler(templateFS), sessions))
	http.Handle("/", auth.Require(log.Request(handler.index()), sessions.Handler(templateFS), sessions))

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

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