package handlers

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

	"mgit.msbls.de/m/paliad/internal/branding"
	"mgit.msbls.de/m/paliad/internal/services"
)

// inviteRequest is the JSON body for POST /api/invite. Both fields are
// optional-ish: email is required and validated, message is a free-form
// personal note the sender may include. Kept minimal on purpose — the
// invitation UX is a single email field plus an optional textarea.
type inviteRequest struct {
	Email   string `json:"email"`
	Message string `json:"message,omitempty"`
}

type inviteResponse struct {
	ID        string `json:"id"`
	Remaining int    `json:"remaining_today"`
}

// POST /api/invite — send a branded invitation email to a colleague.
//
// Auth: any onboarded user may invite. The firm email-domain whitelist
// (ALLOWED_EMAIL_DOMAINS) is enforced in-service so callers can't bypass it
// by bypassing the handler.
//
// Rate limit: 10 invitations per 24h per user, enforced in InviteService.
// The response always includes `remaining_today` so the UI can show
// "3 invitations left" without a second request.
func handleInvite(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	if dbSvc.invite == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "invitations unavailable — SMTP not configured",
		})
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	me, err := dbSvc.users.GetByID(r.Context(), uid)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
		return
	}
	if me == nil {
		writeJSON(w, http.StatusForbidden, map[string]string{
			"error": "onboarding required before sending invitations",
		})
		return
	}

	var req inviteRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}

	id, err := dbSvc.invite.Send(r.Context(), uid,
		services.Inviter{
			DisplayName: me.DisplayName,
			Email:       me.Email,
			Lang:        me.Lang,
		},
		services.InviteInput{
			ToEmail: req.Email,
			Message: req.Message,
		},
	)
	if err != nil {
		switch {
		case errors.Is(err, services.ErrInviteInvalidEmail):
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": "invalid email address",
			})
		case errors.Is(err, services.ErrInviteDomainBlocked):
			writeJSON(w, http.StatusForbidden, map[string]string{
				"error": "recipient domain not on the " + branding.Name + " allow-list",
			})
		case errors.Is(err, services.ErrInviteRateLimited):
			w.Header().Set("Retry-After", "3600")
			writeJSON(w, http.StatusTooManyRequests, map[string]string{
				"error": "invitation rate limit reached — try again tomorrow",
			})
		default:
			writeJSON(w, http.StatusInternalServerError, map[string]string{
				"error": "failed to send invitation",
			})
		}
		return
	}

	writeJSON(w, http.StatusCreated, inviteResponse{
		ID:        id.String(),
		Remaining: dbSvc.invite.RemainingToday(uid),
	})
}

// GET /api/invite — read-only status: how many invitation slots the caller
// has left in the current 24h window. Cheap to call; frontend can poll this
// before opening the modal to avoid disabling the send button post-hoc.
func handleInviteStatus(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	if dbSvc.invite == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "invitations unavailable — SMTP not configured",
		})
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	writeJSON(w, http.StatusOK, map[string]int{
		"remaining_today": dbSvc.invite.RemainingToday(uid),
		"limit":           services.InviteRateLimit,
	})
}
