package main

import (
	"context"
	"crypto/sha256"
	"embed"
	"fmt"
	"log"
	"net/http"

	"github.com/jmoiron/sqlx"
	_ "modernc.org/sqlite" // Imported for side effects: registers DB driver

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

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

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

type Handler struct {
	db *sqlx.DB
}

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

	log.Println(msg)

	http.Error(w, msg, status)
}

func hashPassword(ctx context.Context, db *sqlx.DB, user, pass string) (string, error) {
	// Look up password salt from DB and hash password with it
	query := `SELECT val FROM state WHERE key = 'pwsalt'`

	var salt []byte
	err := db.GetContext(ctx, &salt, query)
	if err != nil {
		return "", err
	}

	h := sha256.New()

	_, err = h.Write(salt)
	if err != nil {
		return "", err
	}

	fmt.Fprint(h, ":", user, ":", pass)

	return fmt.Sprintf("%02x", h.Sum(nil)), nil
}

type authProvider struct {
	db *sqlx.DB
}

func (a authProvider) Valid(ctx context.Context, userName, pass string) (*auth.User, error) {
	query := `SELECT count(*) FROM users;`

	var count int
	err := a.db.GetContext(ctx, &count, query)
	if err != nil {
		return nil, err
	}

	user := &auth.User{
		Name: userName,
	}

	// If no entry in user DB, just allow everything, so that initial user creation works.
	if count == 0 {
		log.Println("user database empty, allowing access by", user, "regardless of password")
		return user, nil
	}

	hashedPw, err := hashPassword(ctx, a.db, userName, pass)

	log.Printf("hashed pw for %s: %s", userName, hashedPw)

	query = `SELECT rowid, name FROM users WHERE name = ? AND password = ?`

	err = a.db.GetContext(ctx, user, query, userName, hashedPw)
	if err != nil {
		return nil, err
	}

	return user, nil
}

func logRequest(r *http.Request) {
	log.Println("handling", r.Method, r.URL, "from", r.RemoteAddr, "by", auth.Get(r))
}

func main() {
	db, err := sqlx.Open("sqlite", "vino.sqlite")
	if err != nil {
		log.Fatalln("can't open DB:", err)
	}
	defer db.Close()

	err = initDB(db)
	if err != nil {
		log.Fatalln("can't initialize DB:", err)
	}

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

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

	handler := Handler{
		db: db,
	}

	ap := authProvider{
		db: db,
	}

	http.HandleFunc("/details/img", auth.Require(http.HandlerFunc(handler.img), ap))
	http.HandleFunc("/details/", auth.Require(http.HandlerFunc(handler.details), ap))
	http.HandleFunc("/", auth.Require(http.HandlerFunc(handler.index), ap))

	const listenAddr = ":7878"

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

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