// broadcasts.go — bulk team-email send (t-paliad-147 / issue #7).
//
// One write endpoint (/api/team/broadcast) and a pair of read endpoints
// for the /admin/broadcasts viewer.
//
// The /api/team/broadcast handler enforces the project-lead-OR-global_admin
// authorisation in BroadcastService.Send, so non-leads receive 403.
package handlers

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

	"github.com/google/uuid"

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

// broadcastRequest is the JSON body for POST /api/team/broadcast.
//
// Recipients carry the addresseelist as resolved on the client side: the
// frontend filters the displayed team table, then submits the user_ids the
// user wanted to mail. The server validates each address and rejects if
// any is malformed.
type broadcastRequest struct {
	ProjectID       *uuid.UUID                  `json:"project_id,omitempty"`
	Subject         string                      `json:"subject"`
	Body            string                      `json:"body"`
	TemplateKey     string                      `json:"template_key,omitempty"`
	Lang            string                      `json:"lang,omitempty"`
	RecipientFilter map[string]any              `json:"recipient_filter,omitempty"`
	Recipients      []broadcastRequestRecipient `json:"recipients"`
}

type broadcastRequestRecipient struct {
	UserID        uuid.UUID `json:"user_id"`
	Email         string    `json:"email"`
	DisplayName   string    `json:"display_name"`
	FirstName     string    `json:"first_name"`
	RoleOnProject string    `json:"role_on_project"`
}

// POST /api/team/broadcast — dispatch a personalised email to a filtered
// team subset. Returns the broadcast ID and per-recipient send report.
func handleTeamBroadcast(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	if dbSvc.broadcast == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "broadcasts unavailable — broadcast service not configured",
		})
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}

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

	in := services.BroadcastInput{
		ProjectID:       req.ProjectID,
		Subject:         req.Subject,
		Body:            req.Body,
		TemplateKey:     req.TemplateKey,
		Lang:            req.Lang,
		RecipientFilter: req.RecipientFilter,
		Recipients:      make([]services.BroadcastRecipient, 0, len(req.Recipients)),
	}
	for _, rc := range req.Recipients {
		in.Recipients = append(in.Recipients, services.BroadcastRecipient{
			UserID:        rc.UserID,
			Email:         rc.Email,
			DisplayName:   rc.DisplayName,
			FirstName:     rc.FirstName,
			RoleOnProject: rc.RoleOnProject,
		})
	}

	report, err := dbSvc.broadcast.Send(r.Context(), uid, in)
	if err != nil {
		switch {
		case errors.Is(err, services.ErrBroadcastForbidden):
			writeJSON(w, http.StatusForbidden, map[string]string{
				"error": "only project leads or global admins can send broadcasts",
			})
		case errors.Is(err, services.ErrBroadcastNoRecipients):
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": "no recipients selected",
			})
		case errors.Is(err, services.ErrBroadcastTooManyRecipients):
			writeJSON(w, http.StatusUnprocessableEntity, map[string]string{
				"error": err.Error(),
			})
		case errors.Is(err, services.ErrBroadcastEmptySubject):
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": "subject is required",
			})
		case errors.Is(err, services.ErrBroadcastEmptyBody):
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": "body is required",
			})
		case errors.Is(err, services.ErrBroadcastInvalidEmail):
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": err.Error(),
			})
		default:
			writeJSON(w, http.StatusInternalServerError, map[string]string{
				"error": "failed to send broadcast",
			})
		}
		return
	}
	writeJSON(w, http.StatusCreated, report)
}

// GET /api/admin/broadcasts — list broadcasts visible to the caller.
// global_admin sees all rows; senders see their own.
//
// Lives behind the gateOnboarded gate (not adminGate) so a project lead
// who's never been promoted to global_admin can still see their own
// sends.
func handleListBroadcasts(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	if dbSvc.broadcast == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "broadcasts unavailable",
		})
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	limit := 50
	if v := r.URL.Query().Get("limit"); v != "" {
		if parsed, err := strconv.Atoi(v); err == nil {
			limit = parsed
		}
	}
	rows, err := dbSvc.broadcast.List(r.Context(), uid, limit)
	if err != nil {
		if errors.Is(err, services.ErrBroadcastForbidden) {
			writeJSON(w, http.StatusForbidden, map[string]string{"error": "forbidden"})
			return
		}
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, rows)
}

// GET /api/admin/broadcasts/{id} — full detail for one broadcast.
func handleGetBroadcast(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	if dbSvc.broadcast == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "broadcasts unavailable",
		})
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid id"})
		return
	}
	detail, err := dbSvc.broadcast.Get(r.Context(), uid, id)
	if err != nil {
		if errors.Is(err, services.ErrBroadcastForbidden) {
			writeJSON(w, http.StatusForbidden, map[string]string{"error": "forbidden"})
			return
		}
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, detail)
}

// GET /admin/broadcasts — server-rendered shell.
func handleAdminBroadcastsPage(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "dist/admin-broadcasts.html")
}
