package devices

import (
	"errors"
	"log/slog"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/api/middleware"
)

const maxRequestBody = 16 << 10

// Handler exposes mobile device token registration endpoints.
type Handler struct {
	svc    *Service
	logger *slog.Logger
}

func NewHandler(db *pgxpool.Pool) *Handler {
	return &Handler{
		svc:    NewService(db),
		logger: slog.Default().With("component", "devices-api"),
	}
}

func (h *Handler) Routes() chi.Router {
	r := chi.NewRouter()
	r.Get("/", h.List)
	r.Post("/register", h.Register)
	r.Post("/unregister", h.Unregister)
	r.Delete("/{id}", h.Delete)
	return r
}

type registerRequest struct {
	Platform  string `json:"platform"`
	App       string `json:"app"`
	PushToken string `json:"push_token"`
	DeviceID  string `json:"device_id"`
}

type unregisterRequest struct {
	PushToken string `json:"push_token"`
}

func (h *Handler) Register(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if claims == nil {
		apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "unauthorized", nil)
		return
	}

	var req registerRequest
	if err := apivalidate.DecodeJSON(w, r, maxRequestBody, &req); err != nil {
		return
	}

	platform := strings.TrimSpace(strings.ToLower(req.Platform))
	app := strings.TrimSpace(req.App)
	pushToken := strings.TrimSpace(req.PushToken)
	deviceID := strings.TrimSpace(req.DeviceID)

	if verr := validateRegister(platform, app, pushToken); verr != nil {
		apivalidate.WriteValidationError(w, r, verr)
		return
	}

	id, err := h.svc.Register(r.Context(), claims.Sub, platform, app, pushToken, deviceID)
	if err != nil {
		if errors.Is(err, ErrUserNotFound) {
			apiresponse.WriteError(w, r, http.StatusNotFound, apiresponse.CodeNotFound, "user not found", nil)
			return
		}
		h.logger.Error("register device", "error", err, "sub", claims.Sub)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"id": id})
}

func (h *Handler) Unregister(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if claims == nil {
		apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "unauthorized", nil)
		return
	}

	var req unregisterRequest
	if err := apivalidate.DecodeJSON(w, r, maxRequestBody, &req); err != nil {
		return
	}
	pushToken := strings.TrimSpace(req.PushToken)
	if pushToken == "" {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "push_token", Message: "required",
		}))
		return
	}

	if err := h.svc.UnregisterByToken(r.Context(), claims.Sub, pushToken); err != nil {
		if errors.Is(err, ErrDeviceNotFound) || errors.Is(err, ErrUserNotFound) {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		h.logger.Error("unregister device", "error", err, "sub", claims.Sub)
		apivalidate.WriteInternal(w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if claims == nil {
		apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "unauthorized", nil)
		return
	}
	id := strings.TrimSpace(chi.URLParam(r, "id"))
	if id == "" {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "id", Message: "required",
		}))
		return
	}

	if err := h.svc.UnregisterByID(r.Context(), claims.Sub, id); err != nil {
		if errors.Is(err, ErrDeviceNotFound) || errors.Is(err, ErrUserNotFound) {
			apivalidate.WriteNotFound(w, r, "device token not found")
			return
		}
		h.logger.Error("delete device", "error", err, "sub", claims.Sub)
		apivalidate.WriteInternal(w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if claims == nil {
		apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "unauthorized", nil)
		return
	}
	list, err := h.svc.List(r.Context(), claims.Sub)
	if err != nil {
		if errors.Is(err, ErrUserNotFound) {
			apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"devices": []Device{}})
			return
		}
		h.logger.Error("list devices", "error", err, "sub", claims.Sub)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"devices": list})
}

func validateRegister(platform, app, pushToken string) *apivalidate.ValidationError {
	if platform != "ios" && platform != "android" {
		return apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "platform", Message: "must be 'ios' or 'android'",
		})
	}
	if app == "" {
		return apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "app", Message: "required",
		})
	}
	if pushToken == "" {
		return apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "push_token", Message: "required",
		})
	}
	return nil
}
