package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"net/http"
	"strings"

	"golang.org/x/crypto/bcrypt"
)

type AuthenticationRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

func (u *EnvUser) Signature(secret string) []byte {
	mac := hmac.New(sha256.New, []byte(secret))

	mac.Write([]byte(u.Password))
	mac.Write([]byte(u.Username))

	return mac.Sum(nil)
}

func (e *Environment) Authenticate(username, password string) *EnvUser {
	user, ok := e.Authentication.lookup[username]
	if !ok {
		return nil
	}

	if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) != nil {
		return nil
	}

	return user
}

func (e *Environment) SignAuthToken(user *EnvUser) string {
	signature := user.Signature(e.Tokens.Secret)

	return user.Username + ":" + hex.EncodeToString(signature)
}

func (e *Environment) VerifyAuthToken(token string) bool {
	index := strings.Index(token, ":")
	if index == -1 {
		return false
	}

	username := token[:index]

	user, ok := e.Authentication.lookup[username]
	if !ok {
		return false
	}

	signature, err := hex.DecodeString(token[index+1:])
	if err != nil {
		return false
	}

	expected := user.Signature(e.Tokens.Secret)

	return hmac.Equal(signature, expected)
}

func IsAuthenticated(r *http.Request) bool {
	if !env.Authentication.Enabled {
		return true
	}

	cookie, err := r.Cookie("whiskr_token")
	if err != nil {
		return false
	}

	return env.VerifyAuthToken(cookie.Value)
}

func Authenticate(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !IsAuthenticated(r) {
			RespondJson(w, http.StatusUnauthorized, map[string]any{
				"error": "unauthorized",
			})

			return
		}

		next.ServeHTTP(w, r)
	})
}

func HandleAuthentication(w http.ResponseWriter, r *http.Request) {
	var request AuthenticationRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		RespondJson(w, http.StatusBadRequest, map[string]any{
			"error": "missing username or password",
		})

		return
	}

	user := env.Authenticate(request.Username, request.Password)
	if user == nil {
		RespondJson(w, http.StatusUnauthorized, map[string]any{
			"error": "invalid username or password",
		})

		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:  "whiskr_token",
		Value: env.SignAuthToken(user),
	})

	RespondJson(w, http.StatusOK, map[string]any{
		"authenticated": true,
	})
}
