package httpserver

import (
	"errors"
	"net"
	"net/http"
	"net/url"
	"strings"

	authservice "update_server/internal/auth"
)

const defaultAdminRedirect = "/admin"

func (h *handler) adminLoginForm(w http.ResponseWriter, r *http.Request) {
	if redirected, err := h.redirectAuthenticatedAdmin(w, r); err != nil {
		http.Error(w, "session lookup failed", http.StatusInternalServerError)
		return
	} else if redirected {
		return
	}

	h.renderLoginPage(w, r, http.StatusOK, "", safeNextPath(r.URL.Query().Get("next")), "")
}

func (h *handler) adminLogin(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid login form", http.StatusBadRequest)
		return
	}

	nextPath := safeNextPath(r.FormValue("next"))
	email := strings.TrimSpace(r.FormValue("email"))
	password := r.FormValue("password")

	token, sessionState, err := h.auth.Authenticate(r.Context(), email, password, clientIP(r), r.UserAgent())
	if err != nil {
		if errors.Is(err, authservice.ErrInvalidCredentials) {
			h.renderLoginPage(w, r, http.StatusUnauthorized, email, nextPath, "Invalid email or password.")
			return
		}

		http.Error(w, "login failed", http.StatusInternalServerError)
		return
	}

	http.SetCookie(w, h.auth.SessionCookie(token, sessionState.Session.ExpiresAt))
	if _, err := h.issueCSRFCookie(w); err != nil {
		http.Error(w, "login failed", http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, nextPath, http.StatusSeeOther)
}

func (h *handler) adminLogout(w http.ResponseWriter, r *http.Request) {
	if cookie, err := r.Cookie(h.auth.SessionCookieName()); err == nil {
		if err := h.auth.InvalidateSession(r.Context(), cookie.Value); err != nil {
			http.Error(w, "logout failed", http.StatusInternalServerError)
			return
		}
	}

	http.SetCookie(w, h.auth.ClearSessionCookie())
	http.SetCookie(w, h.clearCSRFCookie())
	http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
}

func (h *handler) renderLoginPage(w http.ResponseWriter, r *http.Request, status int, email, nextPath, errorMessage string) {
	hasActiveAdmin, err := h.store.Users.HasActiveAdmin(r.Context())
	if err != nil {
		http.Error(w, "admin status lookup failed", http.StatusInternalServerError)
		return
	}

	setupHint := ""
	if !hasActiveAdmin {
		setupHint = "No active admin user exists yet. Set ADMIN_EMAIL and ADMIN_PASSWORD, then restart the server once to bootstrap the first admin account."
	}

	loginData := LoginPageData{
		PageData: h.basePageData(
			r,
			"Admin Login",
			"Secure Sign-In",
			"Admin Login",
			"Use your admin credentials to open the protected server-rendered dashboard.",
		),
		Login: LoginFormData{
			Action:    "/admin/login",
			Email:     email,
			Next:      nextPath,
			Error:     errorMessage,
			SetupHint: setupHint,
		},
	}

	renderPageStatus(w, h.renderer, "login", status, loginData)
}

func (h *handler) redirectAuthenticatedAdmin(w http.ResponseWriter, r *http.Request) (bool, error) {
	cookie, err := r.Cookie(h.auth.SessionCookieName())
	if err != nil {
		return false, nil
	}

	sessionState, err := h.auth.LoadSession(r.Context(), cookie.Value)
	if err != nil {
		if errors.Is(err, authservice.ErrUnauthenticated) {
			http.SetCookie(w, h.auth.ClearSessionCookie())
			return false, nil
		}

		return false, err
	}

	http.Redirect(w, r, safeNextPath(r.URL.Query().Get("next")), http.StatusSeeOther)
	_ = sessionState
	return true, nil
}

func safeNextPath(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return defaultAdminRedirect
	}

	if !strings.HasPrefix(raw, "/") || strings.HasPrefix(raw, "//") {
		return defaultAdminRedirect
	}

	return raw
}

func loginRedirectPath(nextPath string) string {
	values := url.Values{}
	if nextPath = safeNextPath(nextPath); nextPath != defaultAdminRedirect {
		values.Set("next", nextPath)
	}

	if encoded := values.Encode(); encoded != "" {
		return "/admin/login?" + encoded
	}

	return "/admin/login"
}

func clientIP(r *http.Request) string {
	host, _, err := net.SplitHostPort(r.RemoteAddr)
	if err == nil {
		return host
	}

	return r.RemoteAddr
}
