package web

import (
	"crypto/rand"
	"database/sql"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/Jaculabilis/intake/core"
	"github.com/Jaculabilis/intake/web/html"
)

var AuthCookieName string = "intake_auth"
var AuthDuration time.Duration = time.Hour * 24 * 7

func newSession(db core.DB) (string, error) {
	bytes := make([]byte, 32)
	_, err := rand.Read(bytes)
	if err != nil {
		return "", err
	}
	session := fmt.Sprintf("%x", bytes)
	expires := int(time.Now().Add(AuthDuration).Unix())
	_, err = db.Exec(`
		insert into sessions (id, expires)
		values (?, ?)
	`, session, expires)
	if err != nil {
		return "", err
	}
	return session, nil
}

func checkSession(db core.DB, session string) (bool, error) {
	row := db.QueryRow(`
		select expires
		from sessions
		where id = ?
	`, session)
	var expires int
	if err := row.Scan(&expires); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return false, nil
		}
		return false, err
	}
	expiration := time.Unix(int64(expires), 0).UTC()
	if time.Now().After(expiration) {
		return false, nil
	}
	return true, nil
}

func renderLoginWithErrorMessage(writer http.ResponseWriter, req *http.Request, message string) {
	// If an htmx interaction caused the auth error, refresh the page to get the login rendered
	if req.Header.Get("HX-Request") != "" {
		writer.Header()["HX-Refresh"] = []string{"true"}
		writer.WriteHeader(http.StatusForbidden)
		return
	}

	data := html.LoginData{Error: message}
	html.Login(writer, data)
}

func (env *Env) authed(handler http.HandlerFunc) http.HandlerFunc {
	return func(writer http.ResponseWriter, req *http.Request) {
		required, err := core.HasPassword(env.db)
		if err != nil {
			renderLoginWithErrorMessage(writer, req, fmt.Sprintf("error: failed to check for password: %v", err))
			return
		}
		if required {
			cookie, err := req.Cookie(AuthCookieName)
			if errors.Is(err, http.ErrNoCookie) {
				renderLoginWithErrorMessage(writer, req, "Your session is expired or invalid")
				return
			}
			if valid, err := checkSession(env.db, cookie.Value); !valid || err != nil {
				renderLoginWithErrorMessage(writer, req, "Your session is expired or invalid")
				return
			}
		}
		handler(writer, req)
	}
}

func (env *Env) login(writer http.ResponseWriter, req *http.Request) {
	if err := req.ParseForm(); err != nil {
		http.Error(writer, fmt.Sprintf("error: failed to parse form: %v", err), http.StatusOK)
		return
	}
	password := req.PostForm.Get("password")

	pass, err := core.CheckPassword(env.db, password)
	if err != nil {
		http.Error(writer, fmt.Sprintf("error: failed to check password: %v", err), http.StatusOK)
		return
	}
	if !pass {
		http.Error(writer, "Incorrect password", http.StatusOK)
		return
	}

	session, err := newSession(env.db)
	if err != nil {
		http.Error(writer, fmt.Sprintf("error: failed to start session: %v", err), http.StatusOK)
		return
	}

	cookie := http.Cookie{
		Name:  AuthCookieName,
		Value: session,
	}
	http.SetCookie(writer, &cookie)
	writer.Header()["HX-Refresh"] = []string{"true"}
	writer.WriteHeader(http.StatusNoContent)
}