package main

import (
	"errors"
	"net/http"
	"time"

	"fn-registry/functions/infra"
)

const (
	cookieName     = "kanban_session"
	sessionTTL     = 7 * 24 * time.Hour
)

type ctxKey string

const userCtxKey ctxKey = "kanban_user_id"

func setSessionCookie(w http.ResponseWriter, token string, expiresAt int64) {
	infra.SessionCookieSet(w, cookieName, token, expiresAt)
}

func clearSessionCookie(w http.ResponseWriter) {
	infra.SessionCookieClear(w, cookieName)
}

func tokenFromRequest(r *http.Request) string {
	return infra.SessionTokenExtract(r, cookieName)
}

// POST /api/auth/register {username, password, display_name?}
func handleRegister(db *DB, flags *FeatureFlags) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !flags.Enabled("registration-enabled") {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusForbidden, Code: "registration_disabled", Message: "user registration is disabled on this instance"})
			return
		}
		var body struct {
			Username    string `json:"username"`
			Password    string `json:"password"`
			DisplayName string `json:"display_name"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		u, err := db.CreateUser(body.Username, body.Password, body.DisplayName)
		if err != nil {
			if errors.Is(err, errUserAlreadyExists) {
				infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusConflict, Code: "user_exists", Message: err.Error()})
				return
			}
			badRequest(w, err.Error())
			return
		}
		infra.HTTPJSONResponse(w, http.StatusCreated, u)
	}
}

// POST /api/auth/login {username, password}
func handleLogin(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var body struct {
			Username string `json:"username"`
			Password string `json:"password"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		u, err := db.Authenticate(body.Username, body.Password)
		if err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "invalid_credentials", Message: "invalid username or password"})
			return
		}
		sess, err := infra.SessionCreate(db.conn, u.ID, sessionTTL, map[string]any{"username": u.Username})
		if err != nil {
			serverError(w, err)
			return
		}
		setSessionCookie(w, sess.Token, sess.ExpiresAt)
		infra.HTTPJSONResponse(w, http.StatusOK, u)
	}
}

// POST /api/auth/logout
func handleLogout(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		token := tokenFromRequest(r)
		if token != "" {
			_ = db.DeleteSessionByToken(token)
		}
		clearSessionCookie(w)
		w.WriteHeader(http.StatusNoContent)
	}
}

// GET /api/me
func handleMe(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		uid, ok := infra.UserIDFromContext(r.Context(), userCtxKey)
		if !ok {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "unauthorized", Message: "no session"})
			return
		}
		u, err := db.GetUserByID(uid)
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, u)
	}
}

// PATCH /api/me { color? }
func handlePatchMe(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		uid, ok := infra.UserIDFromContext(r.Context(), userCtxKey)
		if !ok {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "unauthorized", Message: "no session"})
			return
		}
		var body struct {
			Color *string `json:"color"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if body.Color != nil {
			if err := db.UpdateUserColor(uid, *body.Color); err != nil {
				serverError(w, err)
				return
			}
		}
		u, err := db.GetUserByID(uid)
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, u)
	}
}

// GET /api/users
func handleListUsers(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		users, err := db.ListUsers()
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, users)
	}
}
