package handlers

import (
	"context"
	"errors"
	"log/slog"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"

	complaint "wucher/internal/domain/complaint"
	easarelease "wucher/internal/domain/easa_release"
	fleetstatus "wucher/internal/domain/fleet_status"
	"wucher/internal/domain/helicopter"
	"wucher/internal/domain/mcf"
	"wucher/internal/shared/pkg/apperrorsx"
	"wucher/internal/shared/pkg/userctx"
	"wucher/internal/shared/pkg/uuidv7"
	requestdto "wucher/internal/transport/http/dto/request"
	responsedto "wucher/internal/transport/http/dto/response"
	"wucher/internal/transport/http/jsonapi"
	"wucher/internal/transport/http/response"
	"wucher/internal/transport/http/validators"
)

type FleetStatusHandler struct {
	svc           fleetstatus.Service
	helicopterSvc helicopter.Service
	complaintSvc  complaintLister
	easaSvc       easaReleaseLister
	historySvc    fleetHistoryLister
	mcfSvc        fleetMCFService
	signoffSvc    complaintSignoffLookup
	storage       fileManagerDownloadURLStorage
	validate      *validators.Validator
}

type FleetStatusDeps struct {
	Helicopter helicopter.Service
	Complaint  complaintLister
	EASA       easaReleaseLister
	History    fleetHistoryLister
	MCF        fleetMCFService
	Signoff    complaintSignoffLookup
	Storage    fileManagerDownloadURLStorage
}
type complaintLister interface {
	ListByHelicopter(ctx context.Context, helicopterID []byte, limit, offset int) ([]complaint.Complaint, int64, error)
	ResolveUserNames(ctx context.Context, complaints []complaint.Complaint) (map[string]string, error)
	ActiveGroundingByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) ([]complaint.Complaint, error)
	OpenByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) ([]complaint.Complaint, error)
}

// fleetMCFService is the MCF access the fleet page needs: pending flags (for status)
// plus the latest MCF per helicopter (for the mcf object / placeholder).
type fleetMCFService interface {
	mcfPendingChecker
	LatestByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]*mcf.MaintenanceCheckFlight, error)
}

type easaReleaseLister interface {
	GetLatestByHelicopter(ctx context.Context, helicopterID []byte) (*easarelease.EASARelease, error)
}

func NewFleetStatusHandler(svc fleetstatus.Service, deps FleetStatusDeps) *FleetStatusHandler {
	if svc == nil || deps.Helicopter == nil || deps.Complaint == nil || deps.EASA == nil ||
		deps.History == nil || deps.MCF == nil || deps.Storage == nil {
		panic("fleet status handler: missing required dependency")
	}
	return &FleetStatusHandler{
		svc:           svc,
		helicopterSvc: deps.Helicopter,
		complaintSvc:  deps.Complaint,
		easaSvc:       deps.EASA,
		historySvc:    deps.History,
		mcfSvc:        deps.MCF,
		signoffSvc:    deps.Signoff,
		storage:       deps.Storage,
		validate:      validators.New(),
	}
}

// CreateFleetStatus godoc
// @Summary Create fleet status
// @Description Create fleet status with maintenance schedules and file attachments.
// @Tags Fleet Status
// @Accept json
// @Produce json
// @Param request body requestdto.FleetStatusCreateRequest true "Fleet status create request"
// @Success 201 {object} responsedto.FleetStatusResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/create [post]
func (h *FleetStatusHandler) Create(c *fiber.Ctx) error {
	var req requestdto.FleetStatusCreateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidJSON, "", nil)
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeFleetStatusValidationErrors(c, fiber.StatusUnprocessableEntity, errs)
	}
	row, err := mapFleetStatusPayload(req.Data.Attributes, actorUserID(c))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidPayload, "", nil)
	}
	if err := h.svc.Create(c.UserContext(), row); err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.Create", err)
	}
	created, err := h.svc.GetByID(c.UserContext(), row.ID)
	if err != nil {
		slog.WarnContext(c.UserContext(), "fleet status re-fetch after create failed", slog.Any("error", err), slog.String("id", idString(row.ID)))
	}
	if created == nil {
		created = row
	}
	createdStatus := h.deriveHelicopterStatus(c.UserContext(), created.Helicopter, created.HelicopterID)
	return response.Write(c, fiber.StatusCreated, fleetStatusDetailResource(c.UserContext(), h, created, createdStatus))
}

// UpdateFleetStatus godoc
// @Summary Update fleet status
// @Description Update fleet status by ID with full replacement for schedules and files.
// @Tags Fleet Status
// @Accept json
// @Produce json
// @Param id path string true "Fleet status ID (UUIDv7)"
// @Param request body requestdto.FleetStatusUpdateRequest true "Fleet status update request"
// @Success 200 {object} responsedto.FleetStatusResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/update/{id} [patch]
func (h *FleetStatusHandler) Update(c *fiber.Ctx) error {
	idStr := c.Params("id")
	id, err := uuidv7.ParseString(idStr)
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"})
	}
	var req requestdto.FleetStatusUpdateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidJSON, "", nil)
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeFleetStatusValidationErrors(c, fiber.StatusUnprocessableEntity, errs)
	}
	if strings.TrimSpace(req.Data.ID) == "" {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusIDRequired, "", &jsonapi.ErrorSource{Pointer: "/data/id"})
	}
	if strings.TrimSpace(req.Data.ID) != idStr {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusIDMismatch, "", &jsonapi.ErrorSource{Pointer: "/data/id"})
	}
	existing, err := h.svc.GetByID(c.UserContext(), id)
	if err != nil || existing == nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "", nil)
	}
	row, err := mergeFleetStatusPatch(existing, req.Data.Attributes, actorUserID(c))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidPayload, "", nil)
	}
	if err := h.svc.Update(c.UserContext(), row); err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.Update", err)
	}
	if req.Data.Attributes.Summary != nil {
		if err := h.svc.ReviseSummary(c.UserContext(), row.HelicopterID, fleetSummaryManualInput(req.Data.Attributes.Summary), actorUserID(c)); err != nil {
			return writeFleetStatusServiceError(c, "FleetStatusHandler.Update", err)
		}
	}
	updated, err := h.svc.GetByID(c.UserContext(), id)
	if err != nil {
		slog.WarnContext(c.UserContext(), "fleet status re-fetch after update failed", slog.Any("error", err), slog.String("id", idString(id)))
	}
	if updated == nil {
		updated = row
	}
	updatedStatus := h.deriveHelicopterStatus(c.UserContext(), updated.Helicopter, updated.HelicopterID)
	return response.Write(c, fiber.StatusOK, fleetStatusDetailResource(c.UserContext(), h, updated, updatedStatus))
}

// DeleteFleetStatus godoc
// @Summary Delete fleet status
// @Description Soft delete fleet status by ID.
// @Tags Fleet Status
// @Produce json
// @Param id path string true "Fleet status ID (UUIDv7)"
// @Success 200 {object} responsedto.FleetStatusDeleteResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/delete/{id} [delete]
func (h *FleetStatusHandler) Delete(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("id"))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"})
	}
	if err := h.svc.Delete(c.UserContext(), id, actorUserID(c)); err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.Delete", err)
	}
	return response.Write(c, fiber.StatusOK, map[string]any{"data": map[string]any{"type": "fleet_status_delete", "attributes": map[string]bool{"deleted": true}}})
}

// GetFleetStatusByID godoc
// @Summary Get fleet status by ID
// @Tags Fleet Status
// @Produce json
// @Param id path string true "Fleet status ID (UUIDv7)"
// @Success 200 {object} responsedto.FleetStatusResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/get/{id} [get]
func (h *FleetStatusHandler) GetByID(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("id"))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"})
	}
	row, err := h.svc.GetByID(c.UserContext(), id)
	if err != nil || row == nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "", nil)
	}
	rowStatus := h.deriveHelicopterStatus(c.UserContext(), row.Helicopter, row.HelicopterID)
	return response.Write(c, fiber.StatusOK, fleetStatusDetailResource(c.UserContext(), h, row, rowStatus))
}

// GetFleetStatusByHelicopterID godoc
// @Summary Get latest fleet status by helicopter ID
// @Tags Fleet Status
// @Produce json
// @Param helicopter_id path string true "Helicopter ID (UUIDv7)"
// @Success 200 {object} responsedto.FleetStatusResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/get-by-helicopter/{helicopter_id} [get]
func (h *FleetStatusHandler) GetByHelicopterID(c *fiber.Ctx) error {
	helicopterID, err := uuidv7.ParseString(c.Params("helicopter_id"))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "helicopter_id is invalid UUID", &jsonapi.ErrorSource{Pointer: "/path/helicopter_id"})
	}
	latest, err := h.svc.LatestByHelicopterIDs(c.UserContext(), [][]byte{helicopterID})
	if err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.GetByHelicopterID", err)
	}
	row := latest[string(helicopterID)]
	if row == nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "fleet status not found for helicopter", nil)
	}
	rowStatus := h.deriveHelicopterStatus(c.UserContext(), row.Helicopter, row.HelicopterID)
	return response.Write(c, fiber.StatusOK, fleetStatusDetailResource(c.UserContext(), h, row, rowStatus))
}

// ListFleetStatus godoc
// @Summary List fleet status
// @Description Lists every helicopter from the master, each with its latest fleet status (or empty data when it has none). Paginated by helicopter.
// @Tags Fleet Status
// @Produce json
// @Param page query int false "Page number" default(1)
// @Param page_size query int false "Page size" default(20)
// @Param filter query string false "Search helicopters by designation/identifier/type"
// @Param sort query string false "Sort helicopters, example: designation"
// @Param status query string false "Filter by operational status; comma-separated for multiple (e.g. available,booked). Values: available, booked, aog, mcf"
// @Param helicopter_id query string false "Filter by helicopter ID (UUIDv7); combined with status uses AND logic"
// @Success 200 {object} responsedto.FleetStatusListResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/get-all [get]
func (h *FleetStatusHandler) List(c *fiber.Ctx) error {
	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	size, _ := strconv.Atoi(c.Query("page_size", "20"))
	if size < 1 {
		size = 20
	}
	offset := (page - 1) * size

	statuses, err := parseHelicopterStatusFilter(c.Query("status"))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid status filter", nil)
	}

	// If helicopter_id is provided, fetch that single helicopter and apply status
	// filter in Go (AND logic). Pagination is irrelevant for a single result.
	if raw := strings.TrimSpace(c.Query("helicopter_id")); raw != "" {
		heliID, err := uuidv7.ParseString(raw)
		if err != nil {
			return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid helicopter_id", &jsonapi.ErrorSource{Pointer: "/query/helicopter_id"})
		}
		resources, err := h.singleHelicopterResources(c.UserContext(), heliID, statuses)
		if err != nil {
			return writeFleetStatusServiceError(c, "FleetStatusHandler.List", err)
		}
		return response.WriteWithMeta(c, fiber.StatusOK, resources, map[string]any{
			"page_number": page,
			"page_size":   size,
			"total":       int64(len(resources)),
		})
	}

	var groundedIDs [][]byte
	if len(statuses) > 0 {
		groundedIDs = groundedHelicopterIDs(c.UserContext(), h.complaintSvc, time.Now().UTC())
	}

	// Drive the listing from the helicopter master so each helicopter carries its
	// latest fleet status (or empty data when it has none).
	helis, total, err := h.helicopterSvc.List(c.UserContext(), c.Query("filter"), statuses, c.Query("sort"), size, offset, groundedIDs)
	if err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.List", err)
	}
	resources, err := h.fleetStatusResourcesForHelicopters(c.UserContext(), helis)
	if err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.List", err)
	}
	sortFleetStatusResourcesByStatus(resources)
	meta := map[string]any{
		"page_number": page,
		"page_size":   size,
		"total":       total,
	}
	return response.WriteWithMeta(c, fiber.StatusOK, resources, meta)
}

// ListFleetStatusDatatable godoc
// @Summary List fleet status (datatable)
// @Description Lists every helicopter from the master, each with its latest fleet status (or empty data when it has none). Paginated by helicopter.
// @Tags Fleet Status
// @Produce json
// @Param draw query int false "Datatable draw counter" default(1)
// @Param start query int false "Datatable start offset" default(0)
// @Param length query int false "Datatable page length" default(10)
// @Param search query string false "Search helicopters by designation/identifier/type"
// @Param sort query string false "Sort helicopters, example: designation"
// @Param status query string false "Filter by operational status; comma-separated for multiple (e.g. available,booked). Values: available, booked, aog, mcf"
// @Param helicopter_id query string false "Filter by helicopter ID (UUIDv7); combined with status uses AND logic"
// @Success 200 {object} responsedto.FleetStatusDataTableResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/get-all/dt [get]
func (h *FleetStatusHandler) ListDatatable(c *fiber.Ctx) error {
	start, _ := strconv.Atoi(c.Query("start", "0"))
	length, _ := strconv.Atoi(c.Query("length", "10"))
	draw, _ := strconv.Atoi(c.Query("draw", "1"))
	if length < 1 {
		length = 10
	}

	statuses, err := parseHelicopterStatusFilter(c.Query("status"))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid status filter", nil)
	}

	// If helicopter_id is provided, fetch that single helicopter and apply status
	// filter in Go (AND logic).
	if raw := strings.TrimSpace(c.Query("helicopter_id")); raw != "" {
		heliID, err := uuidv7.ParseString(raw)
		if err != nil {
			return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid helicopter_id", &jsonapi.ErrorSource{Pointer: "/query/helicopter_id"})
		}
		resources, err := h.singleHelicopterResources(c.UserContext(), heliID, statuses)
		if err != nil {
			return writeFleetStatusServiceError(c, "FleetStatusHandler.ListDatatable", err)
		}
		total := int64(len(resources))
		return response.WriteWithMeta(c, fiber.StatusOK, resources, map[string]any{
			"draw":             draw,
			"records_total":    total,
			"records_filtered": total,
		})
	}

	var groundedIDs [][]byte
	if len(statuses) > 0 {
		groundedIDs = groundedHelicopterIDs(c.UserContext(), h.complaintSvc, time.Now().UTC())
	}

	helis, total, err := h.helicopterSvc.List(c.UserContext(), c.Query("search"), statuses, c.Query("sort"), length, start, groundedIDs)
	if err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.ListDatatable", err)
	}
	resources, err := h.fleetStatusResourcesForHelicopters(c.UserContext(), helis)
	if err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.ListDatatable", err)
	}
	sortFleetStatusResourcesByStatus(resources)
	meta := map[string]any{
		"draw":             draw,
		"records_total":    total,
		"records_filtered": total,
	}
	return response.WriteWithMeta(c, fiber.StatusOK, resources, meta)
}

func fleetStatusResourceStatus(r responsedto.FleetStatusResource) string {
	if r.Attributes.Helicopter != nil {
		return r.Attributes.Helicopter.Status
	}
	return ""
}

// sortFleetStatusResourcesByStatus stably orders the resources by status rank, keeping
// the incoming (designation/sort) order within the same status.
func sortFleetStatusResourcesByStatus(resources []responsedto.FleetStatusResource) {
	sort.SliceStable(resources, func(i, j int) bool {
		return helicopter.StatusRank(fleetStatusResourceStatus(resources[i])) < helicopter.StatusRank(fleetStatusResourceStatus(resources[j]))
	})
}

// MarkFleetStatusServiced godoc
// @Summary Mark fleet status serviced
// @Description Move current maintenance schedules into service logs, then clear active schedules.
// @Tags Fleet Status
// @Produce json
// @Param id path string true "Fleet status ID (UUIDv7)"
// @Success 200 {object} responsedto.FleetStatusMarkServicedResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/mark-serviced/{id} [post]
func (h *FleetStatusHandler) MarkServiced(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("id"))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"})
	}

	if err := h.svc.MarkServiced(c.UserContext(), id, time.Now().UTC(), actorUserID(c)); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "", nil)
		}
		return writeFleetStatusServiceError(c, "FleetStatusHandler.MarkServiced", err)
	}

	return response.Write(c, fiber.StatusOK, map[string]any{
		"data": map[string]any{
			"type": "fleet_status_mark_serviced",
			"attributes": map[string]any{
				"serviced": true,
			},
		},
	})
}

// ListFleetStatusServiceHistory godoc
// @Summary List fleet history (timeline) by helicopter
// @Description Unified timeline: maintenance servicing events merged with complaint/MEL/NSR/action/MCF/EASA/AOG action events, newest first.
// @Tags Fleet Status
// @Produce json
// @Param helicopter_id path string true "Helicopter ID (UUIDv7)"
// @Param page query int false "Page number" default(1)
// @Param page_size query int false "Page size" default(20)
// @Success 200 {object} responsedto.FleetHistoryListResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/fleet-status/history/{helicopter_id} [get]
func (h *FleetStatusHandler) ListHistoryByHelicopter(c *fiber.Ctx) error {
	helicopterID, err := uuidv7.ParseString(c.Params("helicopter_id"))
	if err != nil {
		return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "helicopter_id is invalid UUID", &jsonapi.ErrorSource{Pointer: "/path/helicopter_id"})
	}

	page, _ := strconv.Atoi(c.Query("page", "1"))
	if page < 1 {
		page = 1
	}
	size, _ := strconv.Atoi(c.Query("page_size", "20"))
	if size < 1 {
		size = 20
	}
	offset := (page - 1) * size

	type timedEntry struct {
		at    time.Time
		entry responsedto.FleetHistoryEntry
	}
	items := make([]timedEntry, 0)

	logs, _, err := h.svc.ListServiceHistoryByHelicopterID(c.UserContext(), helicopterID, 1000, 0)
	if err != nil {
		return writeFleetStatusServiceError(c, "FleetStatusHandler.ListHistoryByHelicopter", err)
	}
	for i := range logs {
		l := &logs[i]
		items = append(items, timedEntry{at: l.ServicedAt.UTC(), entry: responsedto.FleetHistoryEntry{
			At:         l.ServicedAt.UTC().Format(time.RFC3339),
			EntityType: "fleet_status",
			EntityID:   idString(l.FleetStatusID),
			Action:     "serviced",
			Message:    "Serviced " + strings.TrimSpace(l.InspectionType+" "+l.Type),
			Actor:      idString(l.CreatedBy),
			ActorName:  userctx.GetDisplayName(c.UserContext(), l.CreatedBy),
		}})
	}

	events, _, herr := h.historySvc.ListByHelicopter(c.UserContext(), helicopterID, 0, 0)
	if herr == nil {
		for i := range events {
			e := &events[i]
			items = append(items, timedEntry{at: e.CreatedAt.UTC(), entry: responsedto.FleetHistoryEntry{
				At:         e.CreatedAt.UTC().Format(time.RFC3339),
				EntityType: e.EntityType,
				EntityID:   idString(e.EntityID),
				Action:     e.Action,
				Message:    e.Message,
				Actor:      idString(e.Actor),
				ActorName:  userctx.GetDisplayName(c.UserContext(), e.Actor),
			}})
		}
	}

	sort.SliceStable(items, func(i, j int) bool { return items[i].at.After(items[j].at) })

	total := int64(len(items))
	start := offset
	if start > len(items) {
		start = len(items)
	}
	end := start + size
	if end > len(items) {
		end = len(items)
	}
	data := make([]responsedto.FleetHistoryEntry, 0, end-start)
	for _, it := range items[start:end] {
		data = append(data, it.entry)
	}
	res := responsedto.FleetHistoryListResponse{Data: data}
	res.Meta.PageNumber = page
	res.Meta.PageSize = size
	res.Meta.Total = total
	return response.Write(c, fiber.StatusOK, res)
}

// singleHelicopterResources fetches one helicopter by ID, builds its fleet
// status resource, and applies an optional status filter (AND logic). Returns
// an empty slice when the helicopter is not found or the status does not match.
func (h *FleetStatusHandler) singleHelicopterResources(ctx context.Context, helicopterID []byte, statuses []string) ([]responsedto.FleetStatusResource, error) {
	heli, err := h.helicopterSvc.GetByID(ctx, helicopterID)
	if err != nil {
		return nil, err
	}
	if heli == nil {
		return []responsedto.FleetStatusResource{}, nil
	}
	resources, err := h.fleetStatusResourcesForHelicopters(ctx, []helicopter.Helicopter{*heli})
	if err != nil {
		return nil, err
	}
	if len(statuses) > 0 && len(resources) > 0 {
		heliStatus := ""
		if resources[0].Attributes.Helicopter != nil {
			heliStatus = resources[0].Attributes.Helicopter.Status
		}
		for _, s := range statuses {
			if s == heliStatus {
				return resources, nil
			}
		}
		return []responsedto.FleetStatusResource{}, nil
	}
	return resources, nil
}

func writeFleetStatusAppError(c *fiber.Ctx, def apperrorsx.Def, detail string, source *jsonapi.ErrorSource) error {
	appErr := apperrorsx.New(def)
	if strings.TrimSpace(detail) != "" {
		appErr.Message = detail
	}
	if source != nil {
		appErr.Source = source
	}
	return apperrorsx.Write(c, appErr)
}

func writeFleetStatusValidationErrors(c *fiber.Ctx, status int, errs []jsonapi.ErrorObject) error {
	statusStr := strconv.Itoa(status)
	enriched := make([]jsonapi.ErrorObject, 0, len(errs))
	for i := range errs {
		e := errs[i]
		e.Status = statusStr
		if strings.TrimSpace(e.Code) == "" || strings.TrimSpace(e.ErrorCode) == "" {
			code, errorCode := inferFleetStatusErrorCode(status, e.Title, e.Detail)
			if strings.TrimSpace(e.Code) == "" {
				e.Code = code
			}
			if strings.TrimSpace(e.ErrorCode) == "" {
				e.ErrorCode = errorCode
			}
		}
		enriched = append(enriched, e)
	}
	return jsonapi.WriteErrors(c, status, enriched)
}

func writeFleetStatusServiceError(c *fiber.Ctx, operation string, err error) error {
	mapped := apperrorsx.Translate(apperrorsx.ModuleFleetStatus, err)
	if mapped != nil && mapped.Code != apperrorsx.CodeInternalServerError {
		logFleetStatusServiceError(c, operation, mapped.Status, mapped.Code, mapped.ErrorCode, err)
		return apperrorsx.Write(c, mapped)
	}

	logFleetStatusServiceError(c, operation, fiber.StatusInternalServerError, apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode, err)
	appErr := apperrorsx.New(apperrorsx.ErrInternal)
	appErr.Message = "an internal error occurred"
	return apperrorsx.Write(c, appErr)
}

func logFleetStatusServiceError(c *fiber.Ctx, operation string, status int, code, errorCode string, err error) {
	requestID := requestIDFromCtx(c)
	args := []any{
		slog.String("request_id", requestID),
		slog.String("operation", strings.TrimSpace(operation)),
		slog.Int("status", status),
		slog.String("code", strings.TrimSpace(code)),
		slog.String("error_code", strings.TrimSpace(errorCode)),
		slog.String("path", c.Path()),
		slog.Any("cause", err),
	}
	if status >= fiber.StatusInternalServerError {
		slog.ErrorContext(c.UserContext(), "fleet status service error", args...)
		return
	}
	slog.WarnContext(c.UserContext(), "fleet status service error", args...)
}

func inferFleetStatusErrorCode(status int, _ string, _ string) (string, string) {
	switch status {
	case fiber.StatusNotFound:
		return apperrorsx.CodeFleetStatusNotFound, apperrorsx.ErrFleetStatusNotFound.ErrorCode
	case fiber.StatusUnprocessableEntity:
		return apperrorsx.CodeFleetStatusInvalidPayload, apperrorsx.ErrFleetStatusInvalidPayload.ErrorCode
	case fiber.StatusBadRequest:
		return apperrorsx.CodeFleetStatusInvalidJSON, apperrorsx.ErrFleetStatusInvalidJSON.ErrorCode
	default:
		return apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode
	}
}
