package auth

import (
	"encoding/json"
	"net/http"

	"nexavpn/backend/internal/apiutil"
	"nexavpn/backend/internal/audit"
	"nexavpn/backend/internal/requestctx"
)

type Handler struct {
	service *Service
	audit   *audit.Service
}

func NewHandler(service *Service, auditService *audit.Service) *Handler {
	return &Handler{service: service, audit: auditService}
}

func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
	var input LoginRequest
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
		return
	}

	response, err := h.service.Login(r.Context(), input.Username, input.Password, r.RemoteAddr, r.UserAgent())
	if err != nil {
		_ = h.audit.Record(r.Context(), audit.Entry{
			EventType:  "auth.login.failed",
			EntityType: "user",
			Status:     "failed",
			Message:    "user login failed",
			Metadata: map[string]any{
				"username": input.Username,
			},
		})
		apiutil.Error(w, http.StatusUnauthorized, "invalid_credentials", "invalid username or password")
		return
	}

	_ = h.audit.Record(r.Context(), audit.Entry{
		ActorUserID: &response.User.ID,
		EventType:   "auth.login",
		EntityType:  "user",
		EntityID:    &response.User.ID,
		Status:      "success",
		Message:     "user login succeeded",
	})
	apiutil.JSON(w, http.StatusOK, response)
}

func (h *Handler) Bootstrap(w http.ResponseWriter, r *http.Request) {
	var input BootstrapRequest
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
		return
	}
	if input.Username == "" || input.Password == "" {
		apiutil.Error(w, http.StatusBadRequest, "validation_error", "username and password are required")
		return
	}
	if input.DisplayName == "" {
		input.DisplayName = input.Username
	}

	user, err := h.service.BootstrapAdmin(r.Context(), input.Username, input.DisplayName, input.Password)
	if err != nil {
		apiutil.Error(w, http.StatusConflict, "bootstrap_failed", "initial admin already exists or could not be created")
		return
	}

	_ = h.audit.Record(r.Context(), audit.Entry{
		ActorUserID: &user.ID,
		EntityType:  "user",
		EntityID:    &user.ID,
		EventType:   "system.bootstrap_admin",
		Status:      "success",
		Message:     "initial admin account created",
	})
	apiutil.JSON(w, http.StatusCreated, user)
}

func (h *Handler) BootstrapStatus(w http.ResponseWriter, r *http.Request) {
	available, err := h.service.BootstrapAvailable(r.Context())
	if err != nil {
		apiutil.Error(w, http.StatusInternalServerError, "bootstrap_status_failed", "unable to determine bootstrap status")
		return
	}

	apiutil.JSON(w, http.StatusOK, map[string]any{
		"bootstrap_available": available,
	})
}

func (h *Handler) Refresh(w http.ResponseWriter, r *http.Request) {
	var input RefreshRequest
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
		return
	}

	response, err := h.service.Refresh(r.Context(), input.RefreshToken)
	if err != nil {
		apiutil.Error(w, http.StatusUnauthorized, "invalid_refresh_token", "unable to refresh session")
		return
	}

	apiutil.JSON(w, http.StatusOK, response)
}

func (h *Handler) Logout(w http.ResponseWriter, r *http.Request) {
	var input RefreshRequest
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
		return
	}

	if err := h.service.Logout(r.Context(), input.RefreshToken); err != nil {
		apiutil.Error(w, http.StatusBadRequest, "logout_failed", "unable to revoke session")
		return
	}

	if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
		_ = h.audit.Record(r.Context(), audit.Entry{
			ActorUserID: &claims.UserID,
			EventType:   "auth.logout",
			EntityType:  "session",
			Status:      "success",
			Message:     "session logout succeeded",
		})
	}
	apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
}

func (h *Handler) Me(w http.ResponseWriter, r *http.Request) {
	claims, ok := requestctx.ClaimsFromContext(r.Context())
	if !ok {
		apiutil.Error(w, http.StatusUnauthorized, "unauthorized", "missing auth claims")
		return
	}

	apiutil.JSON(w, http.StatusOK, map[string]any{
		"id":       claims.UserID,
		"username": claims.Username,
		"role":     claims.Role,
	})
}
