package server

import (
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strings"
	"time"

	httputil "git.mstar.dev/mstar/goutils/http"
	"github.com/rs/zerolog/hlog"

	"git.mstar.dev/mstar/linstrom/storage"
)

func forceCorrectPasskeyAuthFlowMiddleware(
	handler http.Handler,
) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log := hlog.FromRequest(r)
		// Don't fuck with the request if not intended for starting to register or login
		if strings.HasSuffix(r.URL.Path, "loginFinish") {
			log.Debug().Msg("Request to finish login method, doing nothing")
			handler.ServeHTTP(w, r)
			return
		} else if strings.HasSuffix(r.URL.Path, "registerFinish") {
			handler.ServeHTTP(w, r)
			// Force unset session cookie here
			w.Header().Del("Set-Cookie")
			http.SetCookie(w, &http.Cookie{
				Name:    "sid",
				Value:   "",
				Path:    "",
				MaxAge:  0,
				Expires: time.UnixMilli(0),
			})
			return
		} else if strings.HasSuffix(r.URL.Path, "loginBegin") {
			fuckWithLoginRequest(w, r, handler)
		} else if strings.HasSuffix(r.URL.Path, "registerBegin") {
			fuckWithRegisterRequest(w, r, handler)
		}
	})
}

func fuckWithRegisterRequest(
	w http.ResponseWriter,
	r *http.Request,
	nextHandler http.Handler,
) {
	log := hlog.FromRequest(r)
	log.Debug().Msg("Messing with register start request")
	store := StorageFromRequest(r)
	if store == nil {
		return
	}
	cookie, cookieErr := r.Cookie("sid")
	var username struct {
		Username string `json:"username"`
	}
	body, _ := io.ReadAll(r.Body)
	log.Debug().Bytes("body", body).Msg("Body of auth begin request")
	err := json.Unmarshal(body, &username)
	if err != nil {
		httputil.HttpErr(
			w,
			HttpErrIdBadRequest,
			"Not a username json object",
			http.StatusBadRequest,
		)
		return
	}
	if cookieErr == nil {
		// Already authenticated, overwrite username to logged in account's name
		// Get session from cookie
		log.Debug().Msg("Session token exists, force overwriting username of register request")
		session, ok := store.GetSession(cookie.Value)
		if !ok {
			log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing")
			httputil.HttpErr(
				w,
				HttpErrIdDbFailure,
				"Passkey session missing",
				http.StatusInternalServerError,
			)
			return
		}
		acc, err := store.FindAccountByPasskeyId(session.UserID)
		// Assume account must exist if a session for it exists
		if err != nil {
			log.Error().Err(err).Msg("Failed to get account from passkey id from session")
			httputil.HttpErr(
				w,
				HttpErrIdDbFailure,
				"Failed to get authenticated account",
				http.StatusInternalServerError,
			)
			return
		}
		// Replace whatever username may be given with username of logged in account
		newBody := strings.ReplaceAll(string(body), username.Username, acc.Username)
		// Assign to request
		r.Body = io.NopCloser(strings.NewReader(newBody))
		r.ContentLength = int64(len(newBody))
		// And pass on
		nextHandler.ServeHTTP(w, r)
	} else {
		// Not authenticated, ensure that no existing name is registered with
		_, err = store.FindLocalAccountByUsername(username.Username)
		log.Debug().Bool("err-equals-not_found", err == storage.ErrEntryNotFound).Send()
		switch err {
		case nil:
			// No error while getting account means account exists, refuse access
			log.Info().
				Str("username", username.Username).
				Msg("Account with same name already exists, preventing login")
			httputil.HttpErr(
				w,
				HttpErrIdAlreadyExists,
				"Account with that name already exists",
				http.StatusBadRequest,
			)
		case storage.ErrEntryNotFound:
			// Didn't find account with that name, give access
			log.Debug().
				Str("username", username.Username).
				Msg("No account with this username exists yet, passing through")
				// Copy original body since previous reader hit EOF
			r.Body = io.NopCloser(strings.NewReader(string(body)))
			r.ContentLength = int64(len(body))
			nextHandler.ServeHTTP(w, r)
		default:
			// Some other error, log it and return appropriate message
			log.Error().
				Err(err).
				Str("username", username.Username).
				Msg("Failed to check if account with username already exists")
			httputil.HttpErr(
				w,
				HttpErrIdDbFailure,
				"Failed to check if account with that name already exists",
				http.StatusInternalServerError,
			)
		}
	}
}

func fuckWithLoginRequest(
	w http.ResponseWriter,
	r *http.Request,
	nextHandler http.Handler,
) {
	log := hlog.FromRequest(r)
	log.Debug().Msg("Messing with login start request")
	store := StorageFromRequest(r)
	if store == nil {
		return
	}
	cookie, cookieErr := r.Cookie("sid")
	var username struct {
		Username string `json:"username"`
	}
	// Force ignore cookie for now
	_ = cookieErr
	var err error = errors.New("placeholder")
	if err == nil {
		// Someone is logged in, overwrite username with logged in account's one
		body, _ := io.ReadAll(r.Body)
		log.Debug().Bytes("body", body).Msg("Body of auth begin request")
		err := json.Unmarshal(body, &username)
		if err != nil {
			httputil.HttpErr(
				w,
				HttpErrIdBadRequest,
				"Not a username json object",
				http.StatusBadRequest,
			)
			return
		}
		session, ok := store.GetSession(cookie.Value)
		if !ok {
			log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing")
			httputil.HttpErr(
				w,
				HttpErrIdDbFailure,
				"Passkey session missing",
				http.StatusInternalServerError,
			)
			return
		}
		acc, err := store.FindAccountByPasskeyId(session.UserID)
		// Assume account must exist if a session for it exists
		if err != nil {
			log.Error().Err(err).Msg("Failed to get account from passkey id from session")
			httputil.HttpErr(
				w,
				HttpErrIdDbFailure,
				"Failed to get authenticated account",
				http.StatusInternalServerError,
			)
			return
		}
		// Replace whatever username may be given with username of logged in account
		newBody := strings.ReplaceAll(string(body), username.Username, acc.Username)
		// Assign to request
		r.Body = io.NopCloser(strings.NewReader(newBody))
		r.ContentLength = int64(len(newBody))
		// And pass on
		nextHandler.ServeHTTP(w, r)
	} else {
		// No one logged in, check if user exists to prevent creating a bugged account
		body, _ := io.ReadAll(r.Body)
		log.Debug().Bytes("body", body).Msg("Body of auth begin request")
		err := json.Unmarshal(body, &username)
		if err != nil {
			httputil.HttpErr(w, HttpErrIdBadRequest, "Not a username json object", http.StatusBadRequest)
			return
		}
		_, err = store.FindLocalAccountByUsername(username.Username)
		switch err {
		case nil:
		// All good, account exists, keep going
		// Do nothing in this branch
		case storage.ErrEntryNotFound:
			// Account doesn't exist, catch it
			httputil.HttpErr(w, HttpErrIdNotFound, "Username not found", http.StatusNotFound)
			return
		default:
			// catch db failures
			log.Error().Err(err).Str("username", username.Username).Msg("Db failure while getting account")
			httputil.HttpErr(w, HttpErrIdDbFailure, "Failed to check for account in db", http.StatusInternalServerError)
			return
		}
		// Restore body as new reader of the same content
		r.Body = io.NopCloser(strings.NewReader(string(body)))
		nextHandler.ServeHTTP(w, r)
	}
}
