package server

import (
	"net/http"

	"galaxy/backend/internal/diplomail"
	"galaxy/backend/internal/server/clientip"
	"galaxy/backend/internal/server/handlers"
	"galaxy/backend/internal/server/httperr"
	"galaxy/backend/internal/server/middleware/basicauth"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

// AdminDiplomailHandlers groups the diplomatic-mail handlers exposed
// under `/api/v1/admin/games/{game_id}/mail` (per-game admin send /
// broadcast). The handler is intentionally separate from
// `AdminMailHandlers`, which owns the unrelated email outbox surface
// under `/api/v1/admin/mail/*`.
type AdminDiplomailHandlers struct {
	svc    *diplomail.Service
	logger *zap.Logger
}

// NewAdminDiplomailHandlers constructs the handler set. svc may be
// nil — in that case every handler returns 501 not_implemented.
func NewAdminDiplomailHandlers(svc *diplomail.Service, logger *zap.Logger) *AdminDiplomailHandlers {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &AdminDiplomailHandlers{svc: svc, logger: logger.Named("http.admin.diplomail")}
}

// Send handles POST /api/v1/admin/games/{game_id}/mail. The body
// shape mirrors the owner route: `target="user"` requires
// `recipient_user_id`; `target="all"` accepts an optional
// `recipients` scope. The authenticated admin username is captured
// from the basicauth context and persisted as `sender_username`.
func (h *AdminDiplomailHandlers) Send() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminDiplomailSend")
	}
	return func(c *gin.Context) {
		username, ok := basicauth.UsernameFromContext(c.Request.Context())
		if !ok || username == "" {
			httperr.Abort(c, http.StatusUnauthorized, httperr.CodeUnauthorized, "admin authentication is required")
			return
		}
		gameID, ok := parseGameIDParam(c)
		if !ok {
			return
		}
		var req userMailSendAdminRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		ctx := c.Request.Context()
		switch req.Target {
		case "", "user":
			recipientID, parseErr := uuid.Parse(req.RecipientUserID)
			if parseErr != nil {
				httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "recipient_user_id must be a valid UUID")
				return
			}
			msg, rcpt, sendErr := h.svc.SendAdminPersonal(ctx, diplomail.SendAdminPersonalInput{
				GameID:          gameID,
				CallerKind:      diplomail.CallerKindAdmin,
				CallerUsername:  username,
				RecipientUserID: recipientID,
				Subject:         req.Subject,
				Body:            req.Body,
				SenderIP:        clientip.ExtractSourceIP(c),
			})
			if sendErr != nil {
				respondDiplomailError(c, h.logger, "admin mail send personal", ctx, sendErr)
				return
			}
			c.JSON(http.StatusCreated, mailMessageDetailToWire(diplomail.InboxEntry{Message: msg, Recipient: rcpt}, true))
		case "all":
			msg, recipients, sendErr := h.svc.SendAdminBroadcast(ctx, diplomail.SendAdminBroadcastInput{
				GameID:         gameID,
				CallerKind:     diplomail.CallerKindAdmin,
				CallerUsername: username,
				RecipientScope: req.Recipients,
				Subject:        req.Subject,
				Body:           req.Body,
				SenderIP:       clientip.ExtractSourceIP(c),
			})
			if sendErr != nil {
				respondDiplomailError(c, h.logger, "admin mail send broadcast", ctx, sendErr)
				return
			}
			c.JSON(http.StatusCreated, mailBroadcastReceiptToWire(msg, recipients))
		default:
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "target must be 'user' or 'all'")
		}
	}
}

// Broadcast handles POST /api/v1/admin/mail/broadcast. Body:
//
//	{
//	  "scope": "selected" | "all_running",
//	  "game_ids": ["..."],
//	  "recipients": "active" | "active_and_removed" | "all_members",
//	  "subject": "...",
//	  "body": "..."
//	}
//
// The handler routes through SendAdminMultiGameBroadcast and returns
// a fan-out receipt describing the message ids created and the
// total recipient count.
func (h *AdminDiplomailHandlers) Broadcast() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminDiplomailBroadcast")
	}
	return func(c *gin.Context) {
		username, ok := basicauth.UsernameFromContext(c.Request.Context())
		if !ok || username == "" {
			httperr.Abort(c, http.StatusUnauthorized, httperr.CodeUnauthorized, "admin authentication is required")
			return
		}
		var req adminDiplomailBroadcastRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		gameIDs := make([]uuid.UUID, 0, len(req.GameIDs))
		for _, raw := range req.GameIDs {
			parsed, err := uuid.Parse(raw)
			if err != nil {
				httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "game_ids must be valid UUIDs")
				return
			}
			gameIDs = append(gameIDs, parsed)
		}
		ctx := c.Request.Context()
		msgs, total, err := h.svc.SendAdminMultiGameBroadcast(ctx, diplomail.SendMultiGameBroadcastInput{
			CallerUsername: username,
			Scope:          req.Scope,
			GameIDs:        gameIDs,
			RecipientScope: req.Recipients,
			Subject:        req.Subject,
			Body:           req.Body,
			SenderIP:       clientip.ExtractSourceIP(c),
		})
		if err != nil {
			respondDiplomailError(c, h.logger, "admin mail broadcast", ctx, err)
			return
		}
		out := adminDiplomailBroadcastResponseWire{
			RecipientCount: total,
			Messages:       make([]adminDiplomailBroadcastMessageWire, 0, len(msgs)),
		}
		for _, m := range msgs {
			out.Messages = append(out.Messages, adminDiplomailBroadcastMessageWire{
				MessageID: m.MessageID.String(),
				GameID:    m.GameID.String(),
				GameName:  m.GameName,
			})
		}
		c.JSON(http.StatusCreated, out)
	}
}

// Cleanup handles POST /api/v1/admin/mail/cleanup. Body:
//
//	{ "older_than_years": 1 }
//
// The endpoint removes every diplomail_messages row whose game
// finished more than the supplied number of years ago. The cascade
// on the recipient and translation tables prunes the per-user state
// in the same transaction. Returns a CleanupResult envelope.
func (h *AdminDiplomailHandlers) Cleanup() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminDiplomailCleanup")
	}
	return func(c *gin.Context) {
		username, ok := basicauth.UsernameFromContext(c.Request.Context())
		if !ok || username == "" {
			httperr.Abort(c, http.StatusUnauthorized, httperr.CodeUnauthorized, "admin authentication is required")
			return
		}
		_ = username
		var req adminDiplomailCleanupRequestWire
		if err := c.ShouldBindJSON(&req); err != nil {
			httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
			return
		}
		ctx := c.Request.Context()
		result, err := h.svc.BulkCleanup(ctx, diplomail.BulkCleanupInput{OlderThanYears: req.OlderThanYears})
		if err != nil {
			respondDiplomailError(c, h.logger, "admin mail cleanup", ctx, err)
			return
		}
		out := adminDiplomailCleanupResponseWire{
			MessagesDeleted: result.MessagesDeleted,
			GameIDs:         make([]string, 0, len(result.GameIDs)),
		}
		for _, id := range result.GameIDs {
			out.GameIDs = append(out.GameIDs, id.String())
		}
		c.JSON(http.StatusOK, out)
	}
}

// List handles GET /api/v1/admin/mail/messages. Supports pagination
// via `page` and `page_size`, plus optional `game_id`, `kind`, and
// `sender_kind` filters.
func (h *AdminDiplomailHandlers) List() gin.HandlerFunc {
	if h.svc == nil {
		return handlers.NotImplemented("adminDiplomailList")
	}
	return func(c *gin.Context) {
		username, ok := basicauth.UsernameFromContext(c.Request.Context())
		if !ok || username == "" {
			httperr.Abort(c, http.StatusUnauthorized, httperr.CodeUnauthorized, "admin authentication is required")
			return
		}
		filter := diplomail.AdminMessageListing{
			Page:       parsePositiveQueryInt(c.Query("page"), 1),
			PageSize:   parsePositiveQueryInt(c.Query("page_size"), 50),
			Kind:       c.Query("kind"),
			SenderKind: c.Query("sender_kind"),
		}
		if raw := c.Query("game_id"); raw != "" {
			parsed, err := uuid.Parse(raw)
			if err != nil {
				httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "game_id must be a valid UUID")
				return
			}
			filter.GameID = &parsed
		}
		ctx := c.Request.Context()
		page, err := h.svc.ListMessagesForAdmin(ctx, filter)
		if err != nil {
			respondDiplomailError(c, h.logger, "admin mail list", ctx, err)
			return
		}
		out := adminDiplomailListResponseWire{
			Total:    page.Total,
			Page:     page.Page,
			PageSize: page.PageSize,
			Items:    make([]adminDiplomailMessageWire, 0, len(page.Items)),
		}
		for _, m := range page.Items {
			entry := adminDiplomailMessageWire{
				MessageID:      m.MessageID.String(),
				GameID:         m.GameID.String(),
				GameName:       m.GameName,
				Kind:           m.Kind,
				SenderKind:     m.SenderKind,
				SenderIP:       m.SenderIP,
				Subject:        m.Subject,
				Body:           m.Body,
				BodyLang:       m.BodyLang,
				BroadcastScope: m.BroadcastScope,
				CreatedAt:      m.CreatedAt.UTC().Format(timestampLayout),
			}
			if m.SenderUserID != nil {
				s := m.SenderUserID.String()
				entry.SenderUserID = &s
			}
			if m.SenderUsername != nil {
				s := *m.SenderUsername
				entry.SenderUsername = &s
			}
			out.Items = append(out.Items, entry)
		}
		c.JSON(http.StatusOK, out)
	}
}

type adminDiplomailBroadcastRequestWire struct {
	Scope      string   `json:"scope"`
	GameIDs    []string `json:"game_ids,omitempty"`
	Recipients string   `json:"recipients,omitempty"`
	Subject    string   `json:"subject,omitempty"`
	Body       string   `json:"body"`
}

type adminDiplomailBroadcastMessageWire struct {
	MessageID string `json:"message_id"`
	GameID    string `json:"game_id"`
	GameName  string `json:"game_name,omitempty"`
}

type adminDiplomailBroadcastResponseWire struct {
	RecipientCount int                                  `json:"recipient_count"`
	Messages       []adminDiplomailBroadcastMessageWire `json:"messages"`
}

type adminDiplomailCleanupRequestWire struct {
	OlderThanYears int `json:"older_than_years"`
}

type adminDiplomailCleanupResponseWire struct {
	MessagesDeleted int      `json:"messages_deleted"`
	GameIDs         []string `json:"game_ids"`
}

type adminDiplomailMessageWire struct {
	MessageID      string  `json:"message_id"`
	GameID         string  `json:"game_id"`
	GameName       string  `json:"game_name,omitempty"`
	Kind           string  `json:"kind"`
	SenderKind     string  `json:"sender_kind"`
	SenderUserID   *string `json:"sender_user_id,omitempty"`
	SenderUsername *string `json:"sender_username,omitempty"`
	SenderIP       string  `json:"sender_ip,omitempty"`
	Subject        string  `json:"subject,omitempty"`
	Body           string  `json:"body"`
	BodyLang       string  `json:"body_lang"`
	BroadcastScope string  `json:"broadcast_scope"`
	CreatedAt      string  `json:"created_at"`
}

type adminDiplomailListResponseWire struct {
	Total    int                          `json:"total"`
	Page     int                          `json:"page"`
	PageSize int                          `json:"page_size"`
	Items    []adminDiplomailMessageWire  `json:"items"`
}
