package handlers

import (
	"bytes"
	"context"
	"errors"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"

	afterflightinspection "wucher/internal/domain/after_flight_inspection"
	beforeflightinspection "wucher/internal/domain/before_flight_inspection"
	"wucher/internal/domain/duty_roster"
	"wucher/internal/domain/flight"
	flightdata "wucher/internal/domain/flight_data"
	flightinspection "wucher/internal/domain/flight_inspection"
	flightinspectionfilechecklist "wucher/internal/domain/flight_inspection_file_checklist"
	flightprepcheck "wucher/internal/domain/flight_prep_check"
	fmreport "wucher/internal/domain/fm_report"
	"wucher/internal/domain/helicopter"
	helicopterfile "wucher/internal/domain/helicopter_file"
	"wucher/internal/domain/mission"
	reserveac "wucher/internal/domain/reserve_ac"
	"wucher/internal/service"
	"wucher/internal/shared/pkg/apperrorsx"
	"wucher/internal/shared/pkg/uuidv7"
	rootdto "wucher/internal/transport/http/dto"
	requestdto "wucher/internal/transport/http/dto/request"
	responsedto "wucher/internal/transport/http/dto/response"
	shareddto "wucher/internal/transport/http/dto/shared"
	"wucher/internal/transport/http/jsonapi"
	"wucher/internal/transport/http/response"
	"wucher/internal/transport/http/validators"
)

type ReserveAcHandler struct {
	svc        reserveac.ReserveAcService
	auth       *service.AuthService
	flight     flight.FlightService
	flightData flightdata.Service
	mission    mission.Service
	aircraft   helicopter.Service
	inspect    flightinspection.FlightInspectionService
	before     beforeflightinspection.Service
	prep       flightprepcheck.Service
	after      afterflightinspection.Service
	fmReport   fmreport.Service
	heliFile   helicopterfile.Service
	fileCk     flightinspectionfilechecklist.Service
	dutyRoster dutyroster.Service
	storage    fileManagerDownloadURLStorage
	takeover   *service.TakeoverService
	validate   *validators.Validator
}

func NewReserveAcHandler(svc reserveac.ReserveAcService) *ReserveAcHandler {
	return &ReserveAcHandler{
		svc:      svc,
		validate: validators.New(),
	}
}

func (h *ReserveAcHandler) WithFlightService(flightSvc flight.FlightService) *ReserveAcHandler {
	h.flight = flightSvc
	return h
}

func (h *ReserveAcHandler) WithFlightDataService(flightDataSvc flightdata.Service) *ReserveAcHandler {
	h.flightData = flightDataSvc
	return h
}

func (h *ReserveAcHandler) WithMissionService(missionSvc mission.Service) *ReserveAcHandler {
	h.mission = missionSvc
	return h
}

func (h *ReserveAcHandler) WithAuthService(authSvc *service.AuthService) *ReserveAcHandler {
	h.auth = authSvc
	return h
}

func (h *ReserveAcHandler) WithHelicopterService(aircraftSvc helicopter.Service) *ReserveAcHandler {
	h.aircraft = aircraftSvc
	return h
}

func (h *ReserveAcHandler) WithFlightInspectionService(inspectionSvc flightinspection.FlightInspectionService) *ReserveAcHandler {
	h.inspect = inspectionSvc
	return h
}

func (h *ReserveAcHandler) WithBeforeFlightInspectionService(beforeSvc beforeflightinspection.Service) *ReserveAcHandler {
	h.before = beforeSvc
	return h
}

func (h *ReserveAcHandler) WithFlightPrepCheckService(prepSvc flightprepcheck.Service) *ReserveAcHandler {
	h.prep = prepSvc
	return h
}

func (h *ReserveAcHandler) WithAfterFlightInspectionService(afterSvc afterflightinspection.Service) *ReserveAcHandler {
	h.after = afterSvc
	return h
}

func (h *ReserveAcHandler) WithFMReportService(reportSvc fmreport.Service) *ReserveAcHandler {
	h.fmReport = reportSvc
	return h
}

func (h *ReserveAcHandler) WithHelicopterFileService(heliFileSvc helicopterfile.Service) *ReserveAcHandler {
	h.heliFile = heliFileSvc
	return h
}

func (h *ReserveAcHandler) WithFlightInspectionFileChecklistService(fileChecklistSvc flightinspectionfilechecklist.Service) *ReserveAcHandler {
	h.fileCk = fileChecklistSvc
	return h
}

func (h *ReserveAcHandler) WithDutyRosterService(dutyRosterSvc dutyroster.Service) *ReserveAcHandler {
	h.dutyRoster = dutyRosterSvc
	return h
}

func (h *ReserveAcHandler) WithFileStorage(storage fileManagerDownloadURLStorage) *ReserveAcHandler {
	h.storage = storage
	return h
}

func writeReserveAcErrors(c *fiber.Ctx, status int, errs []jsonapi.ErrorObject) error {
	return response.WriteErrors(c, status, apperrorsx.EnrichErrorObjects(status, errs))
}

type reserveAcUpdateCoordinator interface {
	UpdateDetailed(context.Context, service.ReserveAcUpdateInput) (*reserveac.ReserveAc, error)
}

type reserveAcCreateCoordinator interface {
	CreateDetailed(context.Context, service.ReserveAcCreateInput) (*reserveac.ReserveAc, error)
}

func normalizeTakeoverDutyRosterDetail(resource *responsedto.DutyRosterResource) {
	if resource == nil {
		return
	}
	resource.Attributes.ShiftTime = responsedto.DutyRosterShiftTime{}
	detail := &resource.Attributes.Detail
	if detail.Pilot == nil {
		detail.Pilot = []responsedto.DutyRosterPilotCrew{}
	}
	if detail.Doctor == nil {
		detail.Doctor = []responsedto.DutyRosterCrew{}
	}
	if detail.Rescuer == nil {
		detail.Rescuer = []responsedto.DutyRosterCrew{}
	}
	if detail.Helper == nil {
		detail.Helper = []responsedto.DutyRosterCrew{}
	}
	if detail.OtherPerson == nil {
		detail.OtherPerson = []responsedto.DutyRosterCrew{}
	}
	for i := range detail.Doctor {
		detail.Doctor[i].ShiftTime = nil
	}
	for i := range detail.Rescuer {
		detail.Rescuer[i].ShiftTime = nil
	}
	for i := range detail.Helper {
		detail.Helper[i].ShiftTime = nil
	}
	for i := range detail.OtherPerson {
		detail.OtherPerson[i].ShiftTime = nil
	}
}

func parseTakeoverUUID(v string, pointer string) ([]byte, *jsonapi.ErrorObject) {
	id, err := uuidv7.ParseString(strings.TrimSpace(v))
	if err != nil {
		return nil, &jsonapi.ErrorObject{
			Status: "422",
			Title:  "Validation error",
			Detail: strings.TrimSpace(pointer) + " is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: pointer},
		}
	}
	return id, nil
}

func parseTakeoverDate(v string) (time.Time, *jsonapi.ErrorObject) {
	d, err := service.ParseDutyRosterDateOnly(v)
	if err != nil {
		return time.Time{}, &jsonapi.ErrorObject{
			Status: "422",
			Title:  "Validation error",
			Detail: "duty_date must be YYYY-MM-DD",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/duty_date"},
		}
	}
	return d, nil
}

// CreateReserveAc godoc
// @Summary Create reserve aircraft assignment
// @Description Create reserve AC, create draft inspection, fill before + prepare inspection payload, and assign to flight.
// @Tags Flights - Reserve AC
// @Accept json
// @Produce json
// @Param request body requestdto.ReserveAcCreateRequest true "Create reserve AC request"
// @Success 201 {object} responsedto.ReserveAcResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Router /api/v1/reserve-acs/create [post]
func (h *ReserveAcHandler) Create(c *fiber.Ctx) error {
	var req requestdto.ReserveAcCreateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: "an internal error occurred",
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	helicopterID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.AircraftID))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "helicopter_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/helicopter_id"},
		}})
	}

	inspectionDate, err := time.Parse("2006-01-02", strings.TrimSpace(req.Data.Attributes.Inspection.InspectionDate))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "inspection.inspection_date must be YYYY-MM-DD",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/inspection/inspection_date"},
		}})
	}
	if !hasReserveAcBeforePayload(req.Data.Attributes.Inspection.Before) {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "inspection.before is required",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/inspection/before"},
		}})
	}
	if !hasReserveAcPreparePayload(req.Data.Attributes.Inspection.Prepare) {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "inspection.prepare is required",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/inspection/prepare"},
		}})
	}
	flightID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.FlightID))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "flight_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_id"},
		}})
	}
	if creator, ok := h.svc.(reserveAcCreateCoordinator); ok {
		created, createErr := creator.CreateDetailed(c.UserContext(), service.ReserveAcCreateInput{
			HelicopterID:   helicopterID,
			FlightID:       flightID,
			InspectionDate: inspectionDate,
			Before:         &req.Data.Attributes.Inspection.Before,
			Prepare:        &req.Data.Attributes.Inspection.Prepare,
			ActorUserID:    actorUserID(c),
			Flight:         h.flight,
			Inspect:        h.inspect,
			BeforeSvc:      h.before,
			Prep:           h.prep,
			HeliFile:       h.heliFile,
			FileChecklist:  h.fileCk,
		})
		if createErr != nil {
			return writeInternalHandlerErrorWithStatus(c, fiber.StatusBadRequest, "ReserveAcHandler.CreateDetailed", createErr)
		}
		return h.writeReserveAcCreateResponse(c, created, flightID)
	}

	if h.flight == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "flight service is not configured",
		}})
	}
	if h.inspect == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "flight inspection service is not configured",
		}})
	}
	if h.before == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "before flight inspection service is not configured",
		}})
	}
	if h.prep == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "flight prep check service is not configured",
		}})
	}
	if h.heliFile == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "helicopter file service is not configured",
		}})
	}
	if h.fileCk == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "flight inspection file checklist service is not configured",
		}})
	}
	flightRow, err := h.flight.GetByID(c.UserContext(), flightID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	if flightRow == nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "flight_id does not exist",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_id"},
		}})
	}
	if len(flightRow.ReserveAcID) == 16 {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "flight already has reserve_ac assigned",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_id"},
		}})
	}

	exists, err := h.svc.AircraftExists(c.UserContext(), helicopterID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	if !exists {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "helicopter_id does not exist",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/helicopter_id"},
		}})
	}

	inspection := &flightinspection.FlightInspection{
		InspectionDate: inspectionDate.UTC(),
		CreatedBy:      actorUserID(c),
		UpdatedBy:      actorUserID(c),
	}
	if err := h.inspect.CreateDraft(c.UserContext(), inspection); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	row := &reserveac.ReserveAc{
		Status:       reserveac.StatusPending,
		AircraftID:   helicopterID,
		InspectionID: inspection.ID,
		CreatedBy:    actorUserID(c),
		UpdatedBy:    actorUserID(c),
	}

	if err := h.svc.Create(c.UserContext(), row); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	beforeReq := &beforeflightinspection.UpsertRequest{
		OilEngineNR1Checked:       req.Data.Attributes.Inspection.Before.OilEngineNR1Checked,
		OilEngineNR2Checked:       req.Data.Attributes.Inspection.Before.OilEngineNR2Checked,
		HydraulicLHChecked:        req.Data.Attributes.Inspection.Before.HydraulicLHChecked,
		HydraulicRHChecked:        req.Data.Attributes.Inspection.Before.HydraulicRHChecked,
		OilTransmissionMGBChecked: req.Data.Attributes.Inspection.Before.OilTransmissionMGBChecked,
		OilTransmissionIGBChecked: req.Data.Attributes.Inspection.Before.OilTransmissionIGBChecked,
		OilTransmissionTGBChecked: req.Data.Attributes.Inspection.Before.OilTransmissionTGBChecked,
		FuelAmount:                req.Data.Attributes.Inspection.Before.FuelAmount,
		FuelUnit:                  req.Data.Attributes.Inspection.Before.FuelUnit,
		FuelAddedAmount:           req.Data.Attributes.Inspection.Before.FuelAddedAmount,
		Note:                      req.Data.Attributes.Inspection.Before.Note,
	}
	if _, err := h.before.Upsert(c.UserContext(), inspection.ID, beforeReq); err != nil {
		_ = h.svc.Delete(c.UserContext(), row.ID, actorUserID(c))
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	if _, err := h.prep.InitializeChecks(c.UserContext(), inspection.ID); err != nil {
		_ = h.svc.Delete(c.UserContext(), row.ID, actorUserID(c))
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	if _, err := h.prep.UpsertByFlightInspection(c.UserContext(), inspection.ID, &flightprepcheck.UpsertRequest{
		NOTAMBriefing:         req.Data.Attributes.Inspection.Prepare.NotamBriefing,
		WeatherBriefing:       req.Data.Attributes.Inspection.Prepare.WeatherBriefing,
		OperationalFlightPlan: req.Data.Attributes.Inspection.Prepare.OperationalFlightPlan,
	}); err != nil {
		_ = h.svc.Delete(c.UserContext(), row.ID, actorUserID(c))
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	if ok, err := h.upsertInspectionFileChecklistSection(
		c,
		inspection.ID,
		helicopterID,
		helicopterfile.SectionBeforeFirstFlightInspection,
		req.Data.Attributes.Inspection.Before.FileChecklist,
		"/data/attributes/inspection/before/file_checklist",
		row.ID,
	); err != nil {
		return err
	} else if !ok {
		return nil
	}
	if ok, err := h.upsertInspectionFileChecklistSection(
		c,
		inspection.ID,
		helicopterID,
		helicopterfile.SectionFlightPreparationAndWB,
		req.Data.Attributes.Inspection.Prepare.FileChecklist,
		"/data/attributes/inspection/prepare/file_checklist",
		row.ID,
	); err != nil {
		return err
	} else if !ok {
		return nil
	}
	flightRow.ReserveAcID = append([]byte(nil), row.ID...)
	flightRow.UpdatedBy = actorUserID(c)
	if err := h.flight.Update(c.UserContext(), flightRow); err != nil {
		_ = h.svc.Delete(c.UserContext(), row.ID, actorUserID(c))
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}

	inspectionSummary, _ := h.buildCreateInspectionSummary(c, inspection.ID, helicopterID)
	row.Status = h.calculateReserveAcStatus(inspectionSummary)
	if updateErr := h.svc.Update(c.UserContext(), row); updateErr != nil {
		_ = h.svc.Delete(c.UserContext(), row.ID, actorUserID(c))
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	aircraftSummary, _ := h.buildAircraftSummary(c, helicopterID)
	created, err := h.svc.GetByID(c.UserContext(), row.ID)
	if err == nil && created != nil {
		return response.Write(c, fiber.StatusCreated, reserveAcResource(created, reserveAcResourceOptions{
			flightID:   flightID,
			inspection: inspectionSummary,
			aircraft:   aircraftSummary,
		}))
	}
	return response.Write(c, fiber.StatusCreated, reserveAcResource(row, reserveAcResourceOptions{
		flightID:   flightID,
		inspection: inspectionSummary,
		aircraft:   aircraftSummary,
	}))
}

// GetReserveAc godoc
// @Summary Get reserve AC by UUID or flight UUID
// @Description Get reserve AC detail. Path `uuid` accepts reserve_ac id, and supports fallback to flight id.
// @Tags Flights - Reserve AC
// @Produce json
// @Param uuid path string true "Reserve AC UUID or Flight UUID"
// @Success 200 {object} responsedto.ReserveAcResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/reserve-acs/get/{uuid} [get]
func (h *ReserveAcHandler) Get(c *fiber.Ctx) error {
	idRaw := strings.TrimSpace(c.Params("id"))
	if idRaw == "" {
		idRaw = strings.TrimSpace(c.Params("uuid"))
	}
	id, err := uuidv7.ParseString(idRaw)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/path/id"},
		}})
	}

	row, err := h.svc.GetByID(c.UserContext(), id)
	if err != nil || row == nil {
		if h.flight != nil {
			flightRow, flightErr := h.flight.GetByID(c.UserContext(), id)
			if flightErr == nil && flightRow != nil && len(flightRow.ReserveAcID) == 16 {
				row, err = h.svc.GetByID(c.UserContext(), flightRow.ReserveAcID)
			}
		}
		if err != nil || row == nil {
			return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
				Status: "404",
				Title:  "Not found",
				Detail: "reserve_ac not found",
			}})
		}
	}

	inspectionSummary, _ := h.buildCreateInspectionSummary(c, row.InspectionID, row.AircraftID)
	aircraftSummary, _ := h.buildAircraftSummary(c, row.AircraftID)
	lastDaily, _ := h.svc.GetLastDailyInspectionByHelicopterID(c.UserContext(), row.AircraftID)
	return response.Write(c, fiber.StatusOK, reserveAcResource(row, reserveAcResourceOptions{
		inspection: inspectionSummary,
		aircraft:   aircraftSummary,
		lastDaily:  reserveAcLastDailyInfo(lastDaily),
	}))
}

// Legacy helper endpoint (no longer routed publicly).
func (h *ReserveAcHandler) GetAfterFlightInspection(c *fiber.Ctx) error {
	inspectionID, err := uuidv7.ParseString(strings.TrimSpace(c.Params("inspection_id")))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "inspection_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/path/inspection_id"},
		}})
	}
	return h.getAfterFlightInspectionByID(c, inspectionID)
}

func (h *ReserveAcHandler) getAfterFlightInspectionByID(c *fiber.Ctx, inspectionID []byte) error {
	if h.after == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Get failed",
			Detail: "after flight inspection service is not configured",
		}})
	}
	if h.inspect == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Get failed",
			Detail: "flight inspection service is not configured",
		}})
	}
	if h.heliFile == nil || h.fileCk == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Get failed",
			Detail: "helicopter file checklist service is not configured",
		}})
	}

	reserveAcRow, err := h.svc.GetActiveByInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Get failed",
			Detail: "an internal error occurred",
		}})
	}
	if reserveAcRow == nil {
		return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "reserve_ac not found for inspection_id",
		}})
	}

	row, err := h.after.GetByFlightInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		if mapped := apperrorsx.Translate(apperrorsx.ModuleAfterFlightInspection, err); mapped != nil && mapped.Code != apperrorsx.CodeInternalServerError {
			return apperrorsx.Write(c, mapped)
		}
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Get failed",
			Detail: "an internal error occurred",
		}})
	}

	inspectionRow, _ := h.inspect.GetByID(c.UserContext(), inspectionID)
	afterSection, err := h.buildInspectionChecklistSection(c, inspectionID, reserveAcRow.AircraftID, helicopterfile.SectionAfterLastFlightInspection)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Get failed",
			Detail: "an internal error occurred",
		}})
	}

	helicopterRow := h.afterFlightInspectionHelicopterResource(c.UserContext(), reserveAcRow)
	summary := h.buildAfterFlightInspectionSummary(c.UserContext(), reserveAcRow)

	return c.Status(fiber.StatusOK).JSON(afterFlightInspectionPayload(inspectionID, inspectionRow, helicopterRow, row, afterSection, summary))
}

// Legacy helper endpoint (no longer routed publicly).
func (h *ReserveAcHandler) UpsertAfterFlightInspection(c *fiber.Ctx) error {
	inspectionID, err := uuidv7.ParseString(strings.TrimSpace(c.Params("inspection_id")))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "inspection_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/path/inspection_id"},
		}})
	}

	var req requestdto.ReserveAcAfterFlightInspectionUpsertRequest
	if err := c.BodyParser(&req); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: "an internal error occurred",
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, errs)
	}
	return h.upsertAfterFlightInspectionByID(c, inspectionID, req.Data.Attributes)
}

func (h *ReserveAcHandler) upsertAfterFlightInspectionByID(c *fiber.Ctx, inspectionID []byte, attrs requestdto.ReserveAcAfterFlightInspectionAttributes) error {
	if h.after == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Update failed",
			Detail: "after flight inspection service is not configured",
		}})
	}
	if h.inspect == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Update failed",
			Detail: "flight inspection service is not configured",
		}})
	}
	if h.heliFile == nil || h.fileCk == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Update failed",
			Detail: "helicopter file checklist service is not configured",
		}})
	}

	reserveAcRow, err := h.svc.GetActiveByInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: "an internal error occurred",
		}})
	}
	if reserveAcRow == nil {
		return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "reserve_ac not found for inspection_id",
		}})
	}

	if ok, detail, err := h.allFlightMissionsComplete(c.UserContext(), reserveAcRow); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: "an internal error occurred",
		}})
	} else if !ok {
		if strings.TrimSpace(detail) == "" {
			detail = "all missions in this flight must have complete flight data before after flight inspection"
		}
		c.Locals(apperrorsx.ContextKeyHTTPErrorDetail, detail)
		def := apperrorsx.ErrFlightDataAfterFlightIncomplete
		return jsonapi.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status:    strconv.Itoa(def.Status),
			Code:      def.Code,
			ErrorCode: def.ErrorCode,
			Title:     def.Title,
			Detail:    detail,
			Source:    &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_inspection_id"},
		}})
	}

	if ok, err := h.validateInspectionChecklistSectionComplete(
		c,
		reserveAcRow.AircraftID,
		helicopterfile.SectionAfterLastFlightInspection,
		attrs.FileChecklist,
		"/data/attributes/file_checklist",
	); err != nil {
		return err
	} else if !ok {
		return nil
	}

	upsertReq := &afterflightinspection.UpsertRequest{
		OilEngineNR1Checked:       attrs.OilEngineNR1Checked,
		OilEngineNR2Checked:       attrs.OilEngineNR2Checked,
		HydraulicLHChecked:        attrs.HydraulicLHChecked,
		HydraulicRHChecked:        attrs.HydraulicRHChecked,
		OilTransmissionMGBChecked: attrs.OilTransmissionMGBChecked,
		OilTransmissionIGBChecked: attrs.OilTransmissionIGBChecked,
		OilTransmissionTGBChecked: attrs.OilTransmissionTGBChecked,
		Note:                      attrs.Note,
	}
	row, err := h.after.Upsert(c.UserContext(), inspectionID, upsertReq)
	if err != nil {
		if mapped := apperrorsx.Translate(apperrorsx.ModuleAfterFlightInspection, err); mapped != nil && mapped.Code != apperrorsx.CodeInternalServerError {
			return apperrorsx.Write(c, mapped)
		}
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: "an internal error occurred",
		}})
	}

	if ok, err := h.upsertInspectionFileChecklistSection(
		c,
		inspectionID,
		reserveAcRow.AircraftID,
		helicopterfile.SectionAfterLastFlightInspection,
		attrs.FileChecklist,
		"/data/attributes/file_checklist",
		nil,
	); err != nil {
		return err
	} else if !ok {
		return nil
	}

	if h.fmReport != nil {
		flightRow, flightErr := h.flight.GetByReserveAcID(c.UserContext(), reserveAcRow.ID)
		if flightErr != nil || flightRow == nil {
			return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
				Status: "500",
				Title:  "Internal Server Error",
				Detail: "failed to load flight for fm report",
			}})
		}
		if _, reportErr := h.fmReport.UpsertForFlight(c.UserContext(), flightRow, inspectionID); reportErr != nil {
			return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
				Status: "500",
				Title:  "Internal Server Error",
				Detail: "failed to upsert fm report",
			}})
		}
	}

	// The after-flight inspection has already been persisted above; this only
	// re-reads data to build the response. A failure here is a server-side
	// rendering error, not a failed update, so return 500 (not 400).
	inspectionRow, _ := h.inspect.GetByID(c.UserContext(), inspectionID)
	afterSection, err := h.buildInspectionChecklistSection(c, inspectionID, reserveAcRow.AircraftID, helicopterfile.SectionAfterLastFlightInspection)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Internal Server Error",
			Detail: "an internal error occurred",
		}})
	}

	helicopterRow := h.afterFlightInspectionHelicopterResource(c.UserContext(), reserveAcRow)
	summary := h.buildAfterFlightInspectionSummary(c.UserContext(), reserveAcRow)

	return c.Status(fiber.StatusOK).JSON(afterFlightInspectionPayload(inspectionID, inspectionRow, helicopterRow, row, afterSection, summary))
}

func (h *ReserveAcHandler) allFlightMissionsComplete(ctx context.Context, reserveAcRow *reserveac.ReserveAc) (bool, string, error) {
	if h == nil || h.flight == nil || h.mission == nil || h.flightData == nil || reserveAcRow == nil || len(reserveAcRow.ID) != 16 {
		return false, "", nil
	}

	flightRow, err := h.flight.GetByReserveAcID(ctx, reserveAcRow.ID)
	if err != nil {
		return false, "", err
	}
	if flightRow == nil || len(flightRow.ID) != 16 {
		return false, "", nil
	}

	missions, err := h.mission.ListByFlightID(ctx, flightRow.ID)
	if err != nil {
		return false, "", err
	}
	if len(missions) == 0 {
		return false, "cannot submit after-flight: this flight has no missions", nil
	}

	missing := make([]string, 0, len(missions))
	for i := range missions {
		fd, ok := h.loadMissionFlightData(ctx, missions[i])
		if !ok || !flightdata.IsComplete(fd) {
			missionID, _ := uuidv7.BytesToString(missions[i].ID)
			label := "mission"
			if missionID != "" {
				label = "mission " + missionID
			}
			if t := strings.TrimSpace(missions[i].Type); t != "" {
				label += " (" + strings.ToUpper(t) + ")"
			}
			if !ok || fd == nil {
				label += " has no flight data"
			} else {
				label += " has incomplete flight data"
			}
			missing = append(missing, label)
		}
	}

	if len(missing) > 0 {
		return false, "cannot submit after-flight: " + strings.Join(missing, "; "), nil
	}

	return true, "", nil
}

// GetLastFlightsInspection godoc
// @Summary Get last flights inspection
// @Description Get after-flight inspection detail and AFI file checklist by query `inspection_id`.
// @Tags Flights - After Flights Inspection
// @Produce json
// @Param inspection_id query string true "Flight Inspection UUID"
// @Success 200 {object} rootdto.AfterFlightInspectionResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/after-flight-inspection/get [get]
func (h *ReserveAcHandler) GetLastFlightsInspection(c *fiber.Ctx) error {
	inspectionID, err := uuidv7.ParseString(strings.TrimSpace(c.Query("inspection_id")))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "inspection_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/query/inspection_id"},
		}})
	}
	return h.getAfterFlightInspectionByID(c, inspectionID)
}

// CreateLastFlightsInspection godoc
// @Summary Create last flights inspection
// @Description Create after-flight inspection and AFI file checklist for a flight inspection.
// @Tags Flights - After Flights Inspection
// @Accept json
// @Produce json
// @Param request body requestdto.LastFlightsInspectionCreateRequest true "Create last flights inspection request"
// @Success 200 {object} rootdto.AfterFlightInspectionResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/after-flight-inspection/create [post]
func (h *ReserveAcHandler) CreateLastFlightsInspection(c *fiber.Ctx) error {
	if h.after == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "after flight inspection service is not configured",
		}})
	}
	var req requestdto.LastFlightsInspectionCreateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: "an internal error occurred",
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, errs)
	}
	inspectionID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.FlightInspectionID))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "flight_inspection_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_inspection_id"},
		}})
	}
	existing, err := h.after.GetByFlightInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}})
	}
	if existing != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "after flight inspection already exists",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_inspection_id"},
		}})
	}
	return h.upsertAfterFlightInspectionByID(c, inspectionID, req.Data.Attributes.ReserveAcAfterFlightInspectionAttributes)
}

// UpdateLastFlightsInspection godoc
// @Summary Update last flights inspection
// @Description Update after-flight inspection and AFI file checklist for a flight inspection.
// @Tags Flights - After Flights Inspection
// @Accept json
// @Produce json
// @Param request body requestdto.LastFlightsInspectionUpdateRequest true "Update last flights inspection request"
// @Success 200 {object} rootdto.AfterFlightInspectionResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/after-flight-inspection/update [patch]
func (h *ReserveAcHandler) UpdateLastFlightsInspection(c *fiber.Ctx) error {
	if h.after == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Update failed",
			Detail: "after flight inspection service is not configured",
		}})
	}
	var req requestdto.LastFlightsInspectionUpdateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: "an internal error occurred",
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, errs)
	}
	inspectionID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.FlightInspectionID))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "flight_inspection_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_inspection_id"},
		}})
	}
	existing, err := h.after.GetByFlightInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: "an internal error occurred",
		}})
	}
	if existing == nil {
		return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "after flight inspection not found",
		}})
	}
	return h.upsertAfterFlightInspectionByID(c, inspectionID, req.Data.Attributes.ReserveAcAfterFlightInspectionAttributes)
}

// DeleteLastFlightsInspection godoc
// @Summary Delete last flights inspection
// @Description Delete after-flight inspection and AFI checklist rows by `flight_inspection_id`.
// @Tags Flights - After Flights Inspection
// @Accept json
// @Produce json
// @Param request body requestdto.LastFlightsInspectionDeleteRequest true "Delete last flights inspection request"
// @Success 200 {object} jsonapi.Document
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/after-flight-inspection/delete [delete]
func (h *ReserveAcHandler) DeleteLastFlightsInspection(c *fiber.Ctx) error {
	var req requestdto.LastFlightsInspectionDeleteRequest
	if err := c.BodyParser(&req); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: "an internal error occurred",
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, errs)
	}
	inspectionID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.FlightInspectionID))
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "flight_inspection_id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_inspection_id"},
		}})
	}
	if h.after == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Delete failed",
			Detail: "after flight inspection service is not configured",
		}})
	}
	if h.heliFile == nil || h.fileCk == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Delete failed",
			Detail: "helicopter file checklist service is not configured",
		}})
	}

	existing, err := h.after.GetByFlightInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: "an internal error occurred",
		}})
	}
	if existing == nil {
		return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "after flight inspection not found",
		}})
	}

	reserveAcRow, err := h.svc.GetActiveByInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: "an internal error occurred",
		}})
	}
	if reserveAcRow == nil {
		return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "reserve_ac not found for inspection_id",
		}})
	}

	if err := h.after.DeleteByFlightInspectionID(c.UserContext(), inspectionID); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: "an internal error occurred",
		}})
	}
	if err := h.deleteInspectionFileChecklistSection(c, inspectionID, reserveAcRow.AircraftID, helicopterfile.SectionAfterLastFlightInspection); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: "an internal error occurred",
		}})
	}

	return response.Write(c, fiber.StatusOK, jsonapi.Resource{
		Type: "last_flights_inspection",
		ID:   idString(inspectionID),
		Attributes: map[string]any{
			"deleted": true,
		},
	})
}

// UpdateReserveAc godoc
// @Summary Update reserve AC
// @Description Update reserve AC using body `data.id`.
// @Tags Flights - Reserve AC
// @Accept json
// @Produce json
// @Param request body requestdto.ReserveAcUpdateRequest true "Update reserve AC request"
// @Success 200 {object} responsedto.ReserveAcResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/reserve-acs/update [patch]
func (h *ReserveAcHandler) Update(c *fiber.Ctx) error {
	var req requestdto.ReserveAcUpdateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: "an internal error occurred",
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	pathID := strings.TrimSpace(c.Params("id"))
	if pathID == "" {
		pathID = strings.TrimSpace(c.Params("uuid"))
	}
	bodyID := strings.TrimSpace(req.Data.ID)
	if pathID == "" && bodyID == "" {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "id is required",
			Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
		}})
	}
	if pathID != "" && bodyID != "" && bodyID != pathID {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "id does not match path id",
			Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
		}})
	}
	targetID := bodyID
	if targetID == "" {
		targetID = pathID
	}
	id, err := uuidv7.ParseString(targetID)
	if err != nil {
		sourcePtr := "/data/id"
		if targetID == pathID && pathID != "" && bodyID == "" {
			sourcePtr = "/path/id"
		}
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: sourcePtr},
		}})
	}

	attrs := req.Data.Attributes
	flightRaw, flightPointer, hasFlight := reserveAcUpdateFlightInput(attrs)
	if attrs.AircraftID == nil && attrs.Inspection == nil && !hasFlight {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "at least one attribute must be provided",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes"},
		}})
	}

	if updater, ok := h.svc.(reserveAcUpdateCoordinator); ok {
		updated, updateErr := updater.UpdateDetailed(c.UserContext(), service.ReserveAcUpdateInput{
			ID:            id,
			ActorUserID:   actorUserID(c),
			AircraftID:    attrs.AircraftID,
			FlightID:      attrs.FlightID,
			Inspection:    attrs.Inspection,
			Flight:        h.flight,
			Inspect:       h.inspect,
			Before:        h.before,
			Prep:          h.prep,
			HeliFile:      h.heliFile,
			FileChecklist: h.fileCk,
		})
		if updateErr != nil {
			return writeReserveAcUpdateError(c, updateErr)
		}
		return h.writeReserveAcUpdateResponse(c, updated)
	}

	existing, err := h.svc.GetByID(c.UserContext(), id)
	if err != nil || existing == nil {
		return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "reserve_ac not found",
		}})
	}

	if hasFlight {
		if h.flight == nil {
			return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
				Status: "500",
				Title:  "Update failed",
				Detail: "flight service is not configured",
			}})
		}
		flightID, parseErr := uuidv7.ParseString(flightRaw)
		if parseErr != nil {
			return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "flight_id is invalid UUID",
				Source: &jsonapi.ErrorSource{Pointer: flightPointer},
			}})
		}

		targetFlight, flightErr := h.flight.GetByID(c.UserContext(), flightID)
		if flightErr != nil {
			return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
				Status: "400",
				Title:  "Update failed",
				Detail: flightErr.Error(),
			}})
		}
		if targetFlight == nil {
			return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "flight_id does not exist",
				Source: &jsonapi.ErrorSource{Pointer: flightPointer},
			}})
		}
		if len(targetFlight.ReserveAcID) == 16 && !bytes.Equal(targetFlight.ReserveAcID, existing.ID) {
			return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "flight already has reserve_ac assigned",
				Source: &jsonapi.ErrorSource{Pointer: flightPointer},
			}})
		}

		currentFlight, currentErr := h.flight.GetByReserveAcID(c.UserContext(), existing.ID)
		if currentErr != nil {
			return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
				Status: "400",
				Title:  "Update failed",
				Detail: currentErr.Error(),
			}})
		}
		if currentFlight != nil && !bytes.Equal(currentFlight.ID, targetFlight.ID) {
			currentFlight.ReserveAcID = nil
			currentFlight.UpdatedBy = actorUserID(c)
			if updateErr := h.flight.Update(c.UserContext(), currentFlight); updateErr != nil {
				return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
					Status: "400",
					Title:  "Update failed",
					Detail: "an internal error occurred",
				}})
			}
		}

		if len(targetFlight.ReserveAcID) != 16 || !bytes.Equal(targetFlight.ReserveAcID, existing.ID) {
			targetFlight.ReserveAcID = append([]byte(nil), existing.ID...)
			targetFlight.UpdatedBy = actorUserID(c)
			if updateErr := h.flight.Update(c.UserContext(), targetFlight); updateErr != nil {
				return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
					Status: "400",
					Title:  "Update failed",
					Detail: "an internal error occurred",
				}})
			}
		}
	}

	if attrs.AircraftID != nil && strings.TrimSpace(*attrs.AircraftID) != "" {
		aircraftValue := strings.TrimSpace(*attrs.AircraftID)
		helicopterID, parseErr := uuidv7.ParseString(aircraftValue)
		if parseErr != nil {
			return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "helicopter_id is invalid UUID",
				Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/helicopter_id"},
			}})
		}
		exists, existsErr := h.svc.AircraftExists(c.UserContext(), helicopterID)
		if existsErr != nil {
			return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
				Status: "400",
				Title:  "Update failed",
				Detail: existsErr.Error(),
			}})
		}
		if !exists {
			return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "helicopter_id does not exist",
				Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/helicopter_id"},
			}})
		}
		existing.AircraftID = helicopterID
	}

	if attrs.Inspection != nil {
		inspectionRow, getErr := h.inspect.GetByID(c.UserContext(), existing.InspectionID)
		if getErr != nil {
			return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
				Status: "400",
				Title:  "Update failed",
				Detail: "an internal error occurred",
			}})
		}
		if inspectionRow == nil {
			return writeReserveAcErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
				Status: "404",
				Title:  "Not found",
				Detail: "flight inspection not found",
			}})
		}

		if attrs.Inspection.InspectionDate != nil {
			inspectionDate, parseErr := time.Parse("2006-01-02", strings.TrimSpace(*attrs.Inspection.InspectionDate))
			if parseErr != nil {
				return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
					Status: "422",
					Title:  "Validation error",
					Detail: "inspection.inspection_date must be YYYY-MM-DD",
					Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/inspection/inspection_date"},
				}})
			}
			inspectionRow.InspectionDate = inspectionDate.UTC()
			inspectionRow.UpdatedBy = actorUserID(c)
			if updateErr := h.inspect.Update(c.UserContext(), inspectionRow); updateErr != nil {
				return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
					Status: "400",
					Title:  "Update failed",
					Detail: "an internal error occurred",
				}})
			}
		}

		if attrs.Inspection.Before != nil {
			beforeReq := &beforeflightinspection.UpsertRequest{
				OilEngineNR1Checked:       attrs.Inspection.Before.OilEngineNR1Checked,
				OilEngineNR2Checked:       attrs.Inspection.Before.OilEngineNR2Checked,
				HydraulicLHChecked:        attrs.Inspection.Before.HydraulicLHChecked,
				HydraulicRHChecked:        attrs.Inspection.Before.HydraulicRHChecked,
				OilTransmissionMGBChecked: attrs.Inspection.Before.OilTransmissionMGBChecked,
				OilTransmissionIGBChecked: attrs.Inspection.Before.OilTransmissionIGBChecked,
				OilTransmissionTGBChecked: attrs.Inspection.Before.OilTransmissionTGBChecked,
				FuelAmount:                attrs.Inspection.Before.FuelAmount,
				FuelUnit:                  attrs.Inspection.Before.FuelUnit,
				FuelAddedAmount:           attrs.Inspection.Before.FuelAddedAmount,
				Note:                      attrs.Inspection.Before.Note,
			}
			if _, upsertErr := h.before.Upsert(c.UserContext(), existing.InspectionID, beforeReq); upsertErr != nil {
				return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
					Status: "400",
					Title:  "Update failed",
					Detail: upsertErr.Error(),
				}})
			}
			if attrs.Inspection.Before.FileChecklist != nil {
				if ok, err := h.upsertInspectionFileChecklistSection(
					c,
					existing.InspectionID,
					existing.AircraftID,
					helicopterfile.SectionBeforeFirstFlightInspection,
					attrs.Inspection.Before.FileChecklist,
					"/data/attributes/inspection/before/file_checklist",
					nil,
				); err != nil {
					return err
				} else if !ok {
					return nil
				}
			}
		}

		if attrs.Inspection.Prepare != nil {
			if _, upsertErr := h.prep.UpsertByFlightInspection(c.UserContext(), existing.InspectionID, &flightprepcheck.UpsertRequest{
				NOTAMBriefing:         attrs.Inspection.Prepare.NotamBriefing,
				WeatherBriefing:       attrs.Inspection.Prepare.WeatherBriefing,
				OperationalFlightPlan: attrs.Inspection.Prepare.OperationalFlightPlan,
			}); upsertErr != nil {
				return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
					Status: "400",
					Title:  "Update failed",
					Detail: upsertErr.Error(),
				}})
			}

			if attrs.Inspection.Prepare.FileChecklist != nil {
				if ok, err := h.upsertInspectionFileChecklistSection(
					c,
					existing.InspectionID,
					existing.AircraftID,
					helicopterfile.SectionFlightPreparationAndWB,
					attrs.Inspection.Prepare.FileChecklist,
					"/data/attributes/inspection/prepare/file_checklist",
					nil,
				); err != nil {
					return err
				} else if !ok {
					return nil
				}
			}
		}
	}

	existing.UpdatedBy = actorUserID(c)
	updatedInspectionSummary, _ := h.buildCreateInspectionSummary(c, existing.InspectionID, existing.AircraftID)
	existing.Status = h.calculateReserveAcStatus(updatedInspectionSummary)
	if err := h.svc.Update(c.UserContext(), existing); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: "an internal error occurred",
		}})
	}

	updated, err := h.svc.GetByID(c.UserContext(), id)
	if err == nil && updated != nil {
		inspectionSummary, _ := h.buildCreateInspectionSummary(c, updated.InspectionID, updated.AircraftID)
		aircraftSummary, _ := h.buildAircraftSummary(c, updated.AircraftID)
		return response.Write(c, fiber.StatusOK, reserveAcResource(updated, reserveAcResourceOptions{
			inspection: inspectionSummary,
			aircraft:   aircraftSummary,
		}))
	}
	inspectionSummary, _ := h.buildCreateInspectionSummary(c, existing.InspectionID, existing.AircraftID)
	aircraftSummary, _ := h.buildAircraftSummary(c, existing.AircraftID)
	return response.Write(c, fiber.StatusOK, reserveAcResource(existing, reserveAcResourceOptions{
		inspection: inspectionSummary,
		aircraft:   aircraftSummary,
	}))
}

func (h *ReserveAcHandler) writeReserveAcUpdateResponse(c *fiber.Ctx, row *reserveac.ReserveAc) error {
	if row == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Update failed",
			Detail: "reserve_ac update returned no row",
		}})
	}

	inspectionSummary, _ := h.buildCreateInspectionSummary(c, row.InspectionID, row.AircraftID)
	aircraftSummary, _ := h.buildAircraftSummary(c, row.AircraftID)
	return response.Write(c, fiber.StatusOK, reserveAcResource(row, reserveAcResourceOptions{
		inspection: inspectionSummary,
		aircraft:   aircraftSummary,
	}))
}

func (h *ReserveAcHandler) writeReserveAcCreateResponse(c *fiber.Ctx, row *reserveac.ReserveAc, flightID []byte) error {
	if row == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Create failed",
			Detail: "reserve_ac create returned no row",
		}})
	}

	inspectionSummary, _ := h.buildCreateInspectionSummary(c, row.InspectionID, row.AircraftID)
	aircraftSummary, _ := h.buildAircraftSummary(c, row.AircraftID)
	return response.Write(c, fiber.StatusCreated, reserveAcResource(row, reserveAcResourceOptions{
		flightID:   flightID,
		inspection: inspectionSummary,
		aircraft:   aircraftSummary,
	}))
}

func writeReserveAcUpdateError(c *fiber.Ctx, err error) error {
	if err == nil {
		return writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Update failed",
			Detail: "reserve_ac update failed",
		}})
	}

	var updateErr *service.ReserveAcUpdateError
	if !errors.As(err, &updateErr) {
		return writeInternalHandlerErrorWithStatus(c, fiber.StatusBadRequest, "ReserveAcHandler.Update", err)
	}

	obj := jsonapi.ErrorObject{
		Status: strconv.Itoa(updateErr.Status),
		Title:  updateErr.Title,
		Detail: updateErr.Detail,
	}
	if updateErr.Pointer != "" {
		obj.Source = &jsonapi.ErrorSource{Pointer: updateErr.Pointer}
	}
	return writeReserveAcErrors(c, updateErr.Status, []jsonapi.ErrorObject{obj})
}

// DeleteReserveAc godoc
// @Summary Delete reserve AC
// @Description Delete reserve AC by body `data.id`.
// @Tags Flights - Reserve AC
// @Accept json
// @Produce json
// @Param request body requestdto.ReserveAcDeleteRequest true "Delete reserve AC request"
// @Success 200 {object} jsonapi.Document
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Router /api/v1/reserve-acs/delete [delete]
func (h *ReserveAcHandler) Delete(c *fiber.Ctx) error {
	var req requestdto.ReserveAcDeleteRequest
	if err := c.BodyParser(&req); err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: "an internal error occurred",
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	idRaw := strings.TrimSpace(req.Data.ID)
	id, err := uuidv7.ParseString(idRaw)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "id is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
		}})
	}

	if err := h.svc.Delete(c.UserContext(), id, actorUserID(c)); err != nil {
		status, errObj := buildServiceErrorObject("Delete failed", err, "reserve ac not found")
		return writeReserveAcErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

	return response.Write(c, fiber.StatusOK, jsonapi.Resource{
		Type: "reserve_ac_delete",
		Attributes: map[string]any{
			"deleted": true,
		},
	})
}

// ListReserveAc godoc
// @Summary List reserve AC
// @Description Supports `search`, `filter[search]`, `sort`, `page[number]`, `page[size]`, `page`, `size`, and `limit`.
// @Tags Flights - Reserve AC
// @Produce json
// @Param search query string false "Search text"
// @Param filter[search] query string false "Search text (JSON:API style)"
// @Param id query string false "Reserve AC UUID (exact match)"
// @Param flight_id query string false "Flight UUID (exact match)"
// @Param sort query string false "Sort field"
// @Param page[number] query int false "Page number"
// @Param page[size] query int false "Page size"
// @Param page query int false "Page number (fallback)"
// @Param size query int false "Page size (fallback)"
// @Param limit query int false "Page size override"
// @Success 200 {object} responsedto.ReserveAcListResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Router /api/v1/reserve-acs/get-all [get]
func (h *ReserveAcHandler) List(c *fiber.Ctx) error {
	userID := actorUserID(c)
	if len(userID) == 0 {
		return writeReserveAcErrors(c, fiber.StatusUnauthorized, []jsonapi.ErrorObject{{
			Status: "401",
			Title:  "Unauthorized",
			Detail: "missing auth context",
		}})
	}
	pageNumber := parseIntDefault(c.Query("page[number]"), 0)
	if pageNumber <= 0 {
		pageNumber = parseIntDefault(c.Query("page"), 1)
	}

	pageSize := parseIntDefault(c.Query("page[size]"), 0)
	if pageSize <= 0 {
		pageSize = parseIntDefault(c.Query("limit"), 0)
	}
	if pageSize <= 0 {
		pageSize = parseIntDefault(c.Query("size"), 20)
	}
	if pageSize > 100 {
		pageSize = 100
	}
	if pageSize < 1 {
		pageSize = 20
	}
	if pageNumber < 1 {
		pageNumber = 1
	}

	offset := (pageNumber - 1) * pageSize
	_ = offset
	search := strings.TrimSpace(c.Query("filter[search]"))
	if search == "" {
		search = strings.TrimSpace(c.Query("search"))
	}
	from := strings.TrimSpace(c.Query("from"))
	to := strings.TrimSpace(c.Query("to"))
	if from == "" {
		from = time.Now().UTC().Format("2006-01-02")
	}
	if to == "" {
		to = from
	}

	groups, total, booked, err := h.buildReserveAcHelicopterGroups(c.UserContext(), userID, search, from, to, 0, 0)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "List failed",
			Detail: "an internal error occurred",
		}})
	}
	return response.WriteWithMeta(c, fiber.StatusOK, groups, map[string]any{
		"page_number":       pageNumber,
		"page_size":         pageSize,
		"total":             total,
		"from":              from,
		"to":                to,
		"timezone":          "UTC",
		"booked_count":      booked,
		"available_count":   total - booked,
		"total_helicopters": total,
	})
}

// ListReserveAcDatatable godoc
// @Summary List reserve AC (datatable)
// @Description Datatable list scoped by auth user. Admin can see all rows; non-admin only sees reserve AC linked to own flight assignments.
// @Tags Flights - Reserve AC
// @Produce json
// @Param search query string false "Search text"
// @Param filter[search] query string false "Search text (JSON:API style)"
// @Param id query string false "Reserve AC UUID (exact match)"
// @Param flight_id query string false "Flight UUID (exact match)"
// @Param sort query string false "Sort field"
// @Param page query int false "Page number (default 1)"
// @Param size query int false "Page size (default 20, max 100)"
// @Param limit query int false "Page size override"
// @Param draw query int false "Draw counter (optional)"
// @Success 200 {object} responsedto.ReserveAcListResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 401 {object} jsonapi.ErrorDocument
// @Router /api/v1/reserve-acs/get-all/dt [get]
func (h *ReserveAcHandler) ListDatatable(c *fiber.Ctx) error {
	userID := actorUserID(c)
	if len(userID) == 0 {
		return writeReserveAcErrors(c, fiber.StatusUnauthorized, []jsonapi.ErrorObject{{
			Status: "401",
			Title:  "Unauthorized",
			Detail: "missing auth context",
		}})
	}
	pageNumber := parseIntDefault(c.Query("page"), 1)
	if pageNumber < 1 {
		pageNumber = 1
	}
	pageSize := parseIntDefault(c.Query("limit"), 0)
	if pageSize <= 0 {
		pageSize = parseIntDefault(c.Query("size"), 20)
	}
	if pageSize < 1 {
		pageSize = 20
	}
	if pageSize > 100 {
		pageSize = 100
	}
	offset := (pageNumber - 1) * pageSize
	_ = offset
	draw := parseIntDefault(c.Query("draw"), pageNumber)
	search := strings.TrimSpace(c.Query("filter[search]"))
	if search == "" {
		search = strings.TrimSpace(c.Query("search"))
	}
	from := strings.TrimSpace(c.Query("from"))
	to := strings.TrimSpace(c.Query("to"))
	if from == "" {
		from = time.Now().UTC().Format("2006-01-02")
	}
	if to == "" {
		to = from
	}
	groups, total, booked, err := h.buildReserveAcHelicopterGroups(c.UserContext(), userID, search, from, to, 0, 0)
	if err != nil {
		return writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "List failed",
			Detail: "an internal error occurred",
		}})
	}

	return response.WriteWithMeta(c, fiber.StatusOK, groups, map[string]any{
		"draw":              draw,
		"records_total":     total,
		"records_filtered":  total,
		"page_number":       pageNumber,
		"page_size":         pageSize,
		"total":             total,
		"from":              from,
		"to":                to,
		"timezone":          "UTC",
		"booked_count":      booked,
		"available_count":   total - booked,
		"total_helicopters": total,
	})
}

func (h *ReserveAcHandler) buildReserveAcHelicopterGroups(ctx context.Context, userID []byte, search, fromRaw, toRaw string, limit, offset int) ([]responsedto.ReserveAcHelicopterGroupResource, int64, int64, error) {
	if h.aircraft == nil || h.flight == nil {
		return []responsedto.ReserveAcHelicopterGroupResource{}, 0, 0, nil
	}
	from, err := time.Parse("2006-01-02", fromRaw)
	if err != nil {
		return nil, 0, 0, err
	}
	to, err := time.Parse("2006-01-02", toRaw)
	if err != nil {
		return nil, 0, 0, err
	}
	if to.Before(from) {
		to = from
	}

	helicopters, _, err := h.aircraft.List(ctx, "", nil, "", 0, 0, nil)
	if err != nil {
		return nil, 0, 0, err
	}
	flights := make([]flight.Flight, 0)
	if h.isReserveAcAdmin(ctx, userID) {
		flights, _, err = h.flight.List(ctx, "", "", "flights.date DESC", 0, 0)
	} else {
		flights, _, err = h.flight.ListByUserID(ctx, userID, 0, 0)
	}
	if err != nil {
		return nil, 0, 0, err
	}

	search = strings.ToLower(strings.TrimSpace(search))
	flightByHelicopter := make(map[string]flight.Flight, len(flights))
	for i := range flights {
		if flights[i].Date.Before(from) || flights[i].Date.After(to.Add(24*time.Hour-time.Nanosecond)) {
			continue
		}
		heliKey := idString(flights[i].TakeoverAcID)
		if heliKey == "" {
			continue
		}
		existing, ok := flightByHelicopter[heliKey]
		if !ok || flights[i].Date.After(existing.Date) {
			flightByHelicopter[heliKey] = flights[i]
		}
	}

	groups := make([]responsedto.ReserveAcHelicopterGroupResource, 0, len(helicopters))
	var bookedCount int64
	for i := range helicopters {
		heli := helicopters[i]
		if search != "" {
			hay := strings.ToLower(strings.TrimSpace(heli.Designation + " " + heli.Identifier + " " + heli.Type))
			if !strings.Contains(hay, search) {
				continue
			}
		}
		heliID := idString(heli.ID)
		flightRow, booked := flightByHelicopter[heliID]
		if booked {
			bookedCount++
		}

		reserveID := ""
		status := "available"
		bookedAt := any(nil)
		var bookedBy map[string]any
		var bfiFields *beforeflightinspection.BeforeFlightInspection
		var fpwbFields *flightprepcheck.FlightPrepCheck
		var reserveRow *reserveac.ReserveAc
		if booked {
			status = "booked"
			reserveID = idString(flightRow.ReserveAcID)
			if len(flightRow.ReserveAcID) == 16 {
				if currentReserve, getErr := h.svc.GetByID(ctx, flightRow.ReserveAcID); getErr == nil && currentReserve != nil {
					reserveRow = currentReserve
					bookedAt = reserveRow.UpdatedAt.UTC().Format(time.RFC3339)
					if h.before != nil {
						bfiFields, _ = h.before.GetByFlightInspectionID(ctx, reserveRow.InspectionID)
					}
					if h.prep != nil {
						fpwbFields, _ = h.prep.GetByFlightInspection(ctx, reserveRow.InspectionID)
					}
					if name := h.resolveReserveAcUserName(ctx, reserveRow.UpdatedBy); name != "" {
						bookedBy = map[string]any{"id": idString(reserveRow.UpdatedBy), "name": name}
					}
				}
			}
		}
		if heli.AirOnGround {
			status = "aog"
		}

		lastDailyRaw, _ := h.svc.GetLastDailyInspectionByHelicopterID(ctx, heli.ID)
		lastDaily := responsedto.ReserveAcLastDailyInspectionDTO{
			ID: nil,
			InspectionBy: responsedto.ReserveAcInspectionBySummary{
				ID:        nil,
				Name:      "",
				ShortName: "",
			},
			InspectionAt: nil,
		}
		if lastDailyRaw != nil {
			lastDaily.ID = nullIfEmpty(idString(flightRow.ReserveAcID))
			lastDaily.InspectionBy.Name = strings.TrimSpace(lastDailyRaw.Name)
			lastDaily.InspectionBy.ShortName = strings.TrimSpace(lastDailyRaw.ShortName)
			lastDaily.InspectionAt = lastDailyRaw.At.UTC().Format(time.RFC3339)
		} else if reserveRow != nil {
			// Fallback: if this is the first reserve for this helicopter, expose reserve actor/time as last inspection baseline.
			lastDaily.ID = nullIfEmpty(idString(reserveRow.ID))
			lastDaily.InspectionBy.ID = nullIfEmpty(idString(reserveRow.UpdatedBy))
			if userName := h.resolveReserveAcUserName(ctx, reserveRow.UpdatedBy); userName != "" {
				lastDaily.InspectionBy.Name = userName
				lastDaily.InspectionBy.ShortName = shortNameFromFullName(userName)
			}
			lastDaily.InspectionAt = reserveRow.UpdatedAt.UTC().Format(time.RFC3339)
		}

		var bookingDetails *responsedto.ReserveAcBookingDetailsDTO
		if booked {
			bookingDetails = &responsedto.ReserveAcBookingDetailsDTO{
				ReserveAcID: nullIfEmpty(reserveID),
				FlightID:    nullIfEmpty(idString(flightRow.ID)),
				MissionCode: nullIfEmpty(ptrStringOrEmpty(flightRow.MissionCode)),
				DutyDate:    nullIfEmpty(dateStringOrEmpty(flightRow.Date)),
				BookedAt:    bookedAt,
				BookedBy:    bookedBy,
			}
		}

		bfi := h.buildReserveAcHelicopterFileItems(ctx, heli.ID, helicopterfile.SectionBeforeFirstFlightInspection)
		fpwb := h.buildReserveAcHelicopterFileItems(ctx, heli.ID, helicopterfile.SectionFlightPreparationAndWB)

		groups = append(groups, responsedto.ReserveAcHelicopterGroupResource{
			Type: "helicopter_reserve_group",
			Attributes: responsedto.ReserveAcHelicopterGroupAttributes{
				Helicopter: responsedto.ReserveAcHelicopterIdentity{
					ID:             heliID,
					Designation:    strings.TrimSpace(heli.Designation),
					Identifier:     strings.TrimSpace(heli.Identifier),
					Type:           strings.TrimSpace(heli.Type),
					Status:         status,
					BookingDetails: bookingDetails,
				},
				LastDailyInspection: lastDaily,
				BFI:                 reserveAcBFISectionDTO(bfiFields, bfi),
				FPWB:                reserveAcFPWBSectionDTO(fpwbFields, fpwb),
			},
		})
	}

	total := int64(len(groups))
	paged := groups
	if limit > 0 {
		if offset < 0 {
			offset = 0
		}
		if offset >= len(groups) {
			paged = []responsedto.ReserveAcHelicopterGroupResource{}
		} else {
			end := offset + limit
			if end > len(groups) {
				end = len(groups)
			}
			paged = groups[offset:end]
		}
	}
	return paged, total, bookedCount, nil
}

func (h *ReserveAcHandler) buildReserveAcHelicopterFileItems(ctx context.Context, helicopterID []byte, section string) []responsedto.ReserveAcHelicopterFileItem {
	if h.heliFile == nil {
		return []responsedto.ReserveAcHelicopterFileItem{}
	}
	files, err := h.heliFile.ListByHelicopterAndSection(ctx, helicopterID, section)
	if err != nil {
		return []responsedto.ReserveAcHelicopterFileItem{}
	}
	out := make([]responsedto.ReserveAcHelicopterFileItem, 0, len(files))
	for i := range files {
		fileName := ""
		url := ""
		if files[i].FileAttachment != nil && files[i].FileAttachment.File != nil {
			fileName = strings.TrimSpace(files[i].FileAttachment.File.Name)
			url = resolveFileManagerDownloadURL(ctx, h.storage, files[i].FileAttachment.File)
		}
		out = append(out, responsedto.ReserveAcHelicopterFileItem{
			ID:         idString(files[i].ID),
			FileName:   fileName,
			URL:        url,
			UploadedAt: files[i].UpdatedAt.UTC().Format(time.RFC3339),
			UploadedBy: idString(files[i].UpdatedBy),
		})
	}
	return out
}

func (h *ReserveAcHandler) resolveReserveAcUserName(ctx context.Context, userID []byte) string {
	if h.auth == nil || len(userID) != 16 {
		return ""
	}
	u, err := h.auth.GetUserByID(ctx, userID)
	if err != nil || u == nil {
		return ""
	}
	return strings.TrimSpace(strings.TrimSpace(u.FirstName) + " " + strings.TrimSpace(u.LastName))
}

func nullIfEmpty(v string) any {
	if strings.TrimSpace(v) == "" {
		return nil
	}
	return v
}

func ptrStringOrEmpty(v *string) string {
	if v == nil {
		return ""
	}
	return strings.TrimSpace(*v)
}

func dateStringOrEmpty(v time.Time) string {
	if v.IsZero() {
		return ""
	}
	return v.UTC().Format("2006-01-02")
}

func shortNameFromFullName(fullName string) string {
	parts := strings.Fields(strings.TrimSpace(fullName))
	if len(parts) == 0 {
		return ""
	}
	if len(parts) == 1 {
		r := []rune(parts[0])
		if len(r) == 0 {
			return ""
		}
		return strings.ToUpper(string(r[0]))
	}
	first := []rune(parts[0])
	last := []rune(parts[len(parts)-1])
	if len(first) == 0 || len(last) == 0 {
		return ""
	}
	return strings.ToUpper(string(first[0]) + string(last[0]))
}

func reserveAcBFISectionDTO(v *beforeflightinspection.BeforeFlightInspection, files []responsedto.ReserveAcHelicopterFileItem) responsedto.ReserveAcBFISection {
	out := responsedto.ReserveAcBFISection{Files: files}
	if v == nil {
		return out
	}
	out.FuelAddedAmount = v.FuelAddedAmount
	out.FuelAmount = v.FuelAmount
	out.FuelUnit = strings.TrimSpace(v.FuelUnit)
	out.HydraulicLHChecked = v.HydraulicLHChecked
	out.HydraulicRHChecked = v.HydraulicRHChecked
	out.OilEngineNR1Checked = v.OilEngineNR1Checked
	out.OilEngineNR2Checked = v.OilEngineNR2Checked
	out.OilTransmissionIGBChecked = v.OilTransmissionIGBChecked
	out.OilTransmissionMGBChecked = v.OilTransmissionMGBChecked
	out.OilTransmissionTGBChecked = v.OilTransmissionTGBChecked
	return out
}

func reserveAcFPWBSectionDTO(v *flightprepcheck.FlightPrepCheck, files []responsedto.ReserveAcHelicopterFileItem) responsedto.ReserveAcFPWBSection {
	out := responsedto.ReserveAcFPWBSection{Files: files}
	if v == nil {
		return out
	}
	out.NotamBriefing = boolPtr(v.NOTAMBriefing)
	out.WeatherBriefing = boolPtr(v.WeatherBriefing)
	out.OperationalFlightPlan = boolPtr(v.OperationalFlightPlan)
	return out
}

func (h *ReserveAcHandler) isReserveAcAdmin(ctx context.Context, userID []byte) bool {
	if h.auth == nil || len(userID) != 16 {
		return false
	}
	user, err := h.auth.GetUserByID(ctx, userID)
	if err != nil || user == nil {
		return false
	}
	roleIDs := make([][]byte, 0, 1+len(user.RoleIDs))
	if len(user.RoleID) == 16 {
		roleIDs = append(roleIDs, user.RoleID)
	}
	roleIDs = append(roleIDs, user.RoleIDs...)
	seen := make(map[string]struct{}, len(roleIDs))
	for i := range roleIDs {
		if len(roleIDs[i]) != 16 {
			continue
		}
		key := idString(roleIDs[i])
		if key == "" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		role, err := h.auth.GetRoleByID(ctx, roleIDs[i])
		if err != nil || role == nil {
			continue
		}
		code := strings.ToLower(strings.TrimSpace(role.Code))
		name := strings.ToLower(strings.TrimSpace(role.Name))
		if code == "admin" || name == "admin" {
			return true
		}
	}
	return false
}

type reserveAcListItem struct {
	row         reserveac.ReserveAc
	flightID    []byte
	placeholder bool
}

func (h *ReserveAcHandler) listReserveAcByAuthUser(ctx context.Context, userID []byte, filter, sortParam string, limit, offset int, idFilterKey, flightIDFilterKey string) ([]reserveAcListItem, int64, error) {
	if h.flight == nil || len(userID) != 16 {
		return []reserveAcListItem{}, 0, nil
	}
	flights, _, err := h.flight.ListByUserID(ctx, userID, 100, 0)
	if err != nil {
		return nil, 0, err
	}
	items := h.buildReserveAcListItemsFromFlights(ctx, flights)
	items = filterReserveAcListItemsByExact(items, idFilterKey, flightIDFilterKey)
	items = filterReserveAcListItemsBySearch(items, filter)
	sortReserveAcListItems(items, sortParam)
	total := int64(len(items))
	return paginateReserveAcListItems(items, limit, offset), total, nil
}

func (h *ReserveAcHandler) collectReserveAcRowsByScope(
	ctx context.Context,
	userID []byte,
	isAdmin bool,
	filter string,
	sortParam string,
	limit int,
	offset int,
	idFilter []byte,
	idFilterKey string,
	flightIDFilter []byte,
	flightIDFilterKey string,
) ([]reserveAcListItem, int64, error) {
	_ = idFilter
	_ = flightIDFilter
	if isAdmin {
		if h.flight == nil {
			return []reserveAcListItem{}, 0, nil
		}
		flights, _, err := h.flight.List(ctx, "", "", "flights.date DESC", 0, 0)
		if err != nil {
			return nil, 0, err
		}
		items := h.buildReserveAcListItemsFromFlights(ctx, flights)
		items = filterReserveAcListItemsByExact(items, idFilterKey, flightIDFilterKey)
		items = filterReserveAcListItemsBySearch(items, filter)
		sortReserveAcListItems(items, sortParam)
		total := int64(len(items))
		return paginateReserveAcListItems(items, limit, offset), total, nil
	}
	return h.listReserveAcByAuthUser(ctx, userID, filter, sortParam, limit, offset, idFilterKey, flightIDFilterKey)
}

func (h *ReserveAcHandler) buildReserveAcListItemsFromFlights(ctx context.Context, flights []flight.Flight) []reserveAcListItem {
	items := make([]reserveAcListItem, 0, len(flights))
	seenReserve := make(map[string]struct{}, len(flights))
	for i := range flights {
		flightID := append([]byte(nil), flights[i].ID...)
		if len(flights[i].ReserveAcID) == 16 {
			key := idString(flights[i].ReserveAcID)
			if key != "" {
				if _, ok := seenReserve[key]; ok {
					continue
				}
				seenReserve[key] = struct{}{}
				if row, err := h.svc.GetByID(ctx, flights[i].ReserveAcID); err == nil && row != nil {
					items = append(items, reserveAcListItem{row: *row, flightID: flightID})
					continue
				}
			}
		}
		items = append(items, reserveAcListItem{
			row: reserveac.ReserveAc{
				Status:     reserveac.StatusPending,
				AircraftID: append([]byte(nil), flights[i].ReserveAcID...),
			},
			flightID:    flightID,
			placeholder: true,
		})
	}
	return items
}

func filterReserveAcListItemsByExact(items []reserveAcListItem, reserveIDFilterKey, flightIDFilterKey string) []reserveAcListItem {
	if reserveIDFilterKey == "" && flightIDFilterKey == "" {
		return items
	}
	out := make([]reserveAcListItem, 0, len(items))
	for i := range items {
		if reserveIDFilterKey != "" && idString(items[i].row.ID) != reserveIDFilterKey {
			continue
		}
		if flightIDFilterKey != "" && idString(items[i].flightID) != flightIDFilterKey {
			continue
		}
		out = append(out, items[i])
	}
	return out
}

func filterReserveAcListItemsBySearch(items []reserveAcListItem, filter string) []reserveAcListItem {
	filter = strings.ToLower(strings.TrimSpace(filter))
	if filter == "" {
		return items
	}
	out := make([]reserveAcListItem, 0, len(items))
	for i := range items {
		if reserveAcMatchFilter(items[i].row, idString(items[i].flightID), filter) {
			out = append(out, items[i])
		}
	}
	return out
}

func paginateReserveAcListItems(items []reserveAcListItem, limit, offset int) []reserveAcListItem {
	if limit <= 0 {
		return items
	}
	if offset < 0 {
		offset = 0
	}
	if offset >= len(items) {
		return []reserveAcListItem{}
	}
	end := offset + limit
	if end > len(items) {
		end = len(items)
	}
	return items[offset:end]
}

func sortReserveAcListItems(items []reserveAcListItem, sortParam string) {
	field := strings.TrimSpace(sortParam)
	desc := false
	if strings.HasPrefix(field, "-") {
		desc = true
		field = strings.TrimPrefix(field, "-")
	}
	switch field {
	case "status", "created_at", "updated_at":
	default:
		field = "created_at"
		desc = true
	}
	sort.SliceStable(items, func(i, j int) bool {
		left := items[i].row
		right := items[j].row
		cmp := 0
		switch field {
		case "status":
			ls := strings.ToLower(strings.TrimSpace(left.Status))
			rs := strings.ToLower(strings.TrimSpace(right.Status))
			switch {
			case ls < rs:
				cmp = -1
			case ls > rs:
				cmp = 1
			default:
				cmp = bytes.Compare(items[i].flightID, items[j].flightID)
			}
		case "updated_at":
			switch {
			case left.UpdatedAt.Before(right.UpdatedAt):
				cmp = -1
			case left.UpdatedAt.After(right.UpdatedAt):
				cmp = 1
			default:
				cmp = bytes.Compare(items[i].flightID, items[j].flightID)
			}
		default:
			switch {
			case left.CreatedAt.Before(right.CreatedAt):
				cmp = -1
			case left.CreatedAt.After(right.CreatedAt):
				cmp = 1
			default:
				cmp = bytes.Compare(items[i].flightID, items[j].flightID)
			}
		}
		if cmp == 0 {
			return false
		}
		if desc {
			return cmp > 0
		}
		return cmp < 0
	})
}

func parseOptionalReserveAcUUIDFilter(raw, sourcePointer string) ([]byte, string, *jsonapi.ErrorObject) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, "", nil
	}
	id, err := uuidv7.ParseString(raw)
	if err != nil {
		return nil, "", &jsonapi.ErrorObject{
			Status: "422",
			Title:  "Validation error",
			Detail: "invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: sourcePointer},
		}
	}
	return id, idString(id), nil
}

func reserveAcMatchFilter(row reserveac.ReserveAc, flightID, filter string) bool {
	if filter == "" {
		return true
	}
	haystacks := []string{
		strings.ToLower(strings.TrimSpace(row.Status)),
		strings.ToLower(strings.TrimSpace(idString(row.ID))),
		strings.ToLower(strings.TrimSpace(idString(row.AircraftID))),
		strings.ToLower(strings.TrimSpace(idString(row.InspectionID))),
		strings.ToLower(strings.TrimSpace(flightID)),
	}
	for i := range haystacks {
		if strings.Contains(haystacks[i], filter) {
			return true
		}
	}
	return false
}

func sortReserveAcRows(rows []reserveac.ReserveAc, sortParam string) {
	field := strings.TrimSpace(sortParam)
	desc := false
	if strings.HasPrefix(field, "-") {
		desc = true
		field = strings.TrimPrefix(field, "-")
	}
	switch field {
	case "status", "created_at", "updated_at":
	default:
		field = "created_at"
		desc = true
	}
	sort.SliceStable(rows, func(i, j int) bool {
		left := rows[i]
		right := rows[j]
		cmp := 0
		switch field {
		case "status":
			ls := strings.ToLower(strings.TrimSpace(left.Status))
			rs := strings.ToLower(strings.TrimSpace(right.Status))
			switch {
			case ls < rs:
				cmp = -1
			case ls > rs:
				cmp = 1
			default:
				switch {
				case left.CreatedAt.Before(right.CreatedAt):
					cmp = -1
				case left.CreatedAt.After(right.CreatedAt):
					cmp = 1
				default:
					cmp = 0
				}
			}
		case "updated_at":
			switch {
			case left.UpdatedAt.Before(right.UpdatedAt):
				cmp = -1
			case left.UpdatedAt.After(right.UpdatedAt):
				cmp = 1
			default:
				cmp = 0
			}
		default:
			switch {
			case left.CreatedAt.Before(right.CreatedAt):
				cmp = -1
			case left.CreatedAt.After(right.CreatedAt):
				cmp = 1
			default:
				cmp = 0
			}
		}
		if cmp == 0 {
			return false
		}
		if desc {
			return cmp > 0
		}
		return cmp < 0
	})
}

type reserveAcResourceOptions struct {
	flightID   []byte
	inspection *shareddto.ReserveAcInspectionSummary
	aircraft   *shareddto.ReserveAcAircraftSummary
	lastDaily  *shareddto.ReserveAcLastDailyInfo
}

func reserveAcResource(row *reserveac.ReserveAc, opts reserveAcResourceOptions) responsedto.ReserveAcResource {
	return responsedto.ReserveAcResource{
		Type: "reserve_ac",
		ID:   idString(row.ID),
		Attributes: shareddto.ReserveAcAttributes{
			Status:     row.Status,
			AircraftID: idString(row.AircraftID),
			Aircraft:   opts.aircraft,
			LastDaily:  opts.lastDaily,
			FlightID:   idString(opts.flightID),
			Inspection: opts.inspection,
			CreatedAt:  row.CreatedAt.UTC().Format(time.RFC3339),
			CreatedBy:  uuidStringOrEmpty(row.CreatedBy),
			UpdatedAt:  row.UpdatedAt.UTC().Format(time.RFC3339),
			UpdatedBy:  uuidStringOrEmpty(row.UpdatedBy),
			DeletedAt:  timeStringOrEmpty(row.DeletedAt),
			DeletedBy:  uuidStringOrEmpty(row.DeletedBy),
		},
	}
}

func reserveAcLastDailyInfo(v *shareddto.LastDailyInspection) *shareddto.ReserveAcLastDailyInfo {
	if v == nil {
		return &shareddto.ReserveAcLastDailyInfo{
			Name:      "",
			ShortName: "",
			Date:      "",
			UTCTime:   "",
		}
	}
	t := v.At.UTC()
	return &shareddto.ReserveAcLastDailyInfo{
		Name:      strings.TrimSpace(v.Name),
		ShortName: strings.TrimSpace(v.ShortName),
		Date:      t.Format("02.01.2006"),
		UTCTime:   t.Format("15:04"),
	}
}

func hasReserveAcBeforePayload(v requestdto.ReserveAcBeforeInspectionAttributes) bool {
	if v.OilEngineNR1Checked != nil || v.OilEngineNR2Checked != nil || v.HydraulicLHChecked != nil || v.HydraulicRHChecked != nil {
		return true
	}
	if v.OilTransmissionMGBChecked != nil || v.OilTransmissionIGBChecked != nil || v.OilTransmissionTGBChecked != nil {
		return true
	}
	if v.FuelAmount != nil || v.FuelUnit != nil || v.FuelAddedAmount != nil || v.Note != nil {
		return true
	}
	return false
}

func hasReserveAcPreparePayload(v requestdto.ReserveAcPrepareInspectionAttributes) bool {
	return v.NotamBriefing != nil || v.WeatherBriefing != nil || v.OperationalFlightPlan != nil
}

func reserveAcUpdateFlightInput(attrs requestdto.ReserveAcUpdateAttributes) (string, string, bool) {
	if attrs.FlightID != nil {
		return strings.TrimSpace(*attrs.FlightID), "/data/attributes/flight_id", true
	}
	return "", "", false
}

func boolPtr(v bool) *bool { return &v }

func (h *ReserveAcHandler) buildCreateInspectionSummary(c *fiber.Ctx, inspectionID []byte, helicopterID []byte) (*shareddto.ReserveAcInspectionSummary, error) {
	inspection, err := h.inspect.GetByID(c.UserContext(), inspectionID)
	if err != nil || inspection == nil {
		return nil, err
	}
	ckRows, err := h.fileCk.ListByInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return nil, err
	}

	isDoneByHeliFileID := make(map[string]bool, len(ckRows))
	for i := range ckRows {
		isDoneByHeliFileID[idString(ckRows[i].HelicopterFileID)] = ckRows[i].IsDone
	}

	beforeRows, err := h.heliFile.ListByHelicopterAndSection(c.UserContext(), helicopterID, helicopterfile.SectionBeforeFirstFlightInspection)
	if err != nil {
		return nil, err
	}
	prepareRows, err := h.heliFile.ListByHelicopterAndSection(c.UserContext(), helicopterID, helicopterfile.SectionFlightPreparationAndWB)
	if err != nil {
		return nil, err
	}
	beforeData, _ := h.before.GetByFlightInspectionID(c.UserContext(), inspectionID)
	prepareData, _ := h.prep.GetByFlightInspection(c.UserContext(), inspectionID)

	beforeSection := h.buildChecklistSectionSummary(c, beforeRows, isDoneByHeliFileID)
	prepareSection := h.buildChecklistSectionSummary(c, prepareRows, isDoneByHeliFileID)
	out := &shareddto.ReserveAcInspectionSummary{
		ID:             idString(inspection.ID),
		InspectionDate: inspection.InspectionDate.UTC().Format("2006-01-02"),
		Before: shareddto.ReserveAcBeforeSectionDTO{
			Total:         beforeSection.Total,
			Completed:     beforeSection.Completed,
			DoneFiles:     beforeSection.DoneFiles,
			FileChecklist: beforeSection.FileChecklist,
		},
		Prepare: shareddto.ReserveAcPrepareSectionDTO{
			Total:         prepareSection.Total,
			Completed:     prepareSection.Completed,
			DoneFiles:     prepareSection.DoneFiles,
			FileChecklist: prepareSection.FileChecklist,
		},
	}
	if beforeData != nil {
		out.Before.Fields = &shareddto.ReserveAcBeforeInspectionFields{
			OilEngineNR1Checked:       beforeData.OilEngineNR1Checked,
			OilEngineNR2Checked:       beforeData.OilEngineNR2Checked,
			HydraulicLHChecked:        beforeData.HydraulicLHChecked,
			HydraulicRHChecked:        beforeData.HydraulicRHChecked,
			OilTransmissionMGBChecked: beforeData.OilTransmissionMGBChecked,
			OilTransmissionIGBChecked: beforeData.OilTransmissionIGBChecked,
			OilTransmissionTGBChecked: beforeData.OilTransmissionTGBChecked,
			FuelAmount:                beforeData.FuelAmount,
			FuelUnit:                  strings.TrimSpace(beforeData.FuelUnit),
			FuelAddedAmount:           beforeData.FuelAddedAmount,
		}
	}
	if prepareData != nil {
		out.Prepare.PrepareFields = &shareddto.ReserveAcPrepareFields{
			NotamBriefing:         boolPtr(prepareData.NOTAMBriefing),
			WeatherBriefing:       boolPtr(prepareData.WeatherBriefing),
			OperationalFlightPlan: boolPtr(prepareData.OperationalFlightPlan),
		}
	}
	return out, nil
}

type reserveAcChecklistSectionCommon struct {
	Total         int
	Completed     int
	DoneFiles     []shareddto.ReserveAcFileChecklistItemDTO
	FileChecklist []shareddto.ReserveAcFileChecklistItemDTO
}

func (h *ReserveAcHandler) buildChecklistSectionSummary(c *fiber.Ctx, files []helicopterfile.HelicopterFile, isDoneByHeliFileID map[string]bool) reserveAcChecklistSectionCommon {
	out := reserveAcChecklistSectionCommon{
		FileChecklist: make([]shareddto.ReserveAcFileChecklistItemDTO, 0, len(files)),
		DoneFiles:     make([]shareddto.ReserveAcFileChecklistItemDTO, 0, len(files)),
	}
	for i := range files {
		if reserveAcChecklistFileCreatedFromTemplate(&files[i]) {
			continue
		}
		fileID := idString(files[i].ID)
		isDone := isDoneByHeliFileID[fileID]
		if isDone {
			out.Completed++
		}
		out.Total++
		fileAttachmentID := ""
		fileName := ""
		if files[i].FileAttachment != nil {
			fileAttachmentID = idString(files[i].FileAttachment.ID)
			if files[i].FileAttachment.File != nil {
				fileName = strings.TrimSpace(files[i].FileAttachment.File.Name)
			}
		}
		downloadURL := ""
		if files[i].FileAttachment != nil && files[i].FileAttachment.File != nil {
			downloadURL = resolveFileManagerDownloadURL(c.UserContext(), h.storage, files[i].FileAttachment.File)
		}
		item := shareddto.ReserveAcFileChecklistItemDTO{
			HelicopterFileID: fileID,
			FileAttachmentID: fileAttachmentID,
			FileName:         fileName,
			DownloadURL:      downloadURL,
			IsDone:           isDone,
		}
		out.FileChecklist = append(out.FileChecklist, item)
		if item.IsDone {
			out.DoneFiles = append(out.DoneFiles, item)
		}
	}
	return out
}

// reserveAcChecklistFileCreatedFromTemplate reports whether a helicopter file row is a
// document instantiated from a template. Those rows should stay hidden from the
// checklist summary so only catalog template entries appear in the UI.
func reserveAcChecklistFileCreatedFromTemplate(row *helicopterfile.HelicopterFile) bool {
	if row == nil {
		return false
	}
	if row.SourceFile != nil {
		return len(row.SourceFile.TemplateUUID) == 16
	}
	if row.FileAttachment != nil && row.FileAttachment.File != nil {
		return len(row.FileAttachment.File.TemplateUUID) == 16
	}
	return false
}

func (h *ReserveAcHandler) buildInspectionChecklistSection(c *fiber.Ctx, inspectionID []byte, helicopterID []byte, section string) (reserveAcChecklistSectionCommon, error) {
	ckRows, err := h.fileCk.ListByInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return reserveAcChecklistSectionCommon{}, err
	}
	isDoneByHeliFileID := make(map[string]bool, len(ckRows))
	for i := range ckRows {
		isDoneByHeliFileID[idString(ckRows[i].HelicopterFileID)] = ckRows[i].IsDone
	}

	templateFiles, err := h.heliFile.ListByHelicopterAndSection(c.UserContext(), helicopterID, section)
	if err != nil {
		return reserveAcChecklistSectionCommon{}, err
	}
	return h.buildChecklistSectionSummary(c, templateFiles, isDoneByHeliFileID), nil
}

type afterFlightInspectionSummary struct {
	MissionCode       string
	MissionType       []string
	TotalFlight       int
	FlightDataCount   int
	FlightTimeSeconds int64
	Route             []string
}

func afterFlightInspectionPayload(
	inspectionID []byte,
	inspectionRow *flightinspection.FlightInspection,
	helicopterRow *responsedto.HelicopterResource,
	row *afterflightinspection.AfterFlightInspection,
	section reserveAcChecklistSectionCommon,
	summary *afterFlightInspectionSummary,
) rootdto.AfterFlightInspectionResponse {
	inspectionIDStr := idString(inspectionID)
	payload := rootdto.AfterFlightInspectionResponse{
		Data: rootdto.AfterFlightInspectionResource{
			Type: "after_flight_inspection",
			Attributes: rootdto.AfterFlightInspectionAttributes{
				FlightInspectionID: inspectionIDStr,
				Total:              section.Total,
				Completed:          section.Completed,
				DoneFileChecklist:  toAfterFlightChecklistItems(section.DoneFiles),
				FileChecklist:      toAfterFlightChecklistItems(section.FileChecklist),
				Helicopter:         helicopterRow,
				MissionType:        []string{},
				Route:              []string{},
			},
		},
	}
	if inspectionRow != nil {
		payload.Data.Attributes.FlightInspection = afterFlightInspectionResource(inspectionRow)
	}
	if summary != nil {
		payload.Data.Attributes.MissionCode = summary.MissionCode
		payload.Data.Attributes.MissionType = append([]string(nil), summary.MissionType...)
		payload.Data.Attributes.TotalFlight = summary.TotalFlight
		payload.Data.Attributes.FlightDataCount = summary.FlightDataCount
		payload.Data.Attributes.FlightTimeSeconds = summary.FlightTimeSeconds
		payload.Data.Attributes.Route = append([]string(nil), summary.Route...)
	}
	if row == nil {
		return payload
	}

	rowID := idString(row.ID)
	payload.Data.ID = rowID
	payload.Data.Attributes.ID = rowID
	payload.Data.Attributes.OilEngineNR1Checked = row.OilEngineNR1Checked
	payload.Data.Attributes.OilEngineNR2Checked = row.OilEngineNR2Checked
	payload.Data.Attributes.HydraulicLHChecked = row.HydraulicLHChecked
	payload.Data.Attributes.HydraulicRHChecked = row.HydraulicRHChecked
	payload.Data.Attributes.OilTransmissionMGBChecked = row.OilTransmissionMGBChecked
	payload.Data.Attributes.OilTransmissionIGBChecked = row.OilTransmissionIGBChecked
	payload.Data.Attributes.OilTransmissionTGBChecked = row.OilTransmissionTGBChecked
	payload.Data.Attributes.Note = row.Note
	payload.Data.Attributes.CreatedAt = row.CreatedAt.UTC().Format(time.RFC3339)
	payload.Data.Attributes.UpdatedAt = row.UpdatedAt.UTC().Format(time.RFC3339)

	return payload
}

func (h *ReserveAcHandler) afterFlightInspectionHelicopterResource(ctx context.Context, reserveAcRow *reserveac.ReserveAc) *responsedto.HelicopterResource {
	if h == nil || reserveAcRow == nil || len(reserveAcRow.AircraftID) != 16 {
		return nil
	}

	helicopterRow := reserveAcRow.Aircraft
	if h.aircraft != nil {
		if fullRow, err := h.aircraft.GetByID(ctx, reserveAcRow.AircraftID); err == nil && fullRow != nil {
			helicopterRow = fullRow
		}
	}
	if helicopterRow == nil {
		return nil
	}

	isInUse := false
	if h.aircraft != nil {
		if inUse, err := h.aircraft.IsInUse(ctx, reserveAcRow.AircraftID); err == nil {
			isInUse = inUse
		}
	}

	var reserveAcProvider helicopterLastDailyInspectionProvider
	if provider, ok := h.svc.(helicopterLastDailyInspectionProvider); ok {
		reserveAcProvider = provider
	}
	res := helicopterResourceWithStorage(ctx, h.storage, reserveAcProvider, helicopterRow, isInUse)
	return &res
}

func (h *ReserveAcHandler) buildAfterFlightInspectionSummary(ctx context.Context, reserveAcRow *reserveac.ReserveAc) *afterFlightInspectionSummary {
	summary := &afterFlightInspectionSummary{}
	if h == nil || h.flight == nil || reserveAcRow == nil || len(reserveAcRow.ID) != 16 {
		return summary
	}

	flights, err := h.flight.ListByReserveAcID(ctx, reserveAcRow.ID)
	if err != nil || len(flights) == 0 {
		return summary
	}

	if h.mission == nil {
		return summary
	}

	missionTypeSeen := make(map[string]struct{})
	route := make([]string, 0)
	for f := range flights {
		if len(flights[f].ID) != 16 {
			continue
		}
		if summary.MissionCode == "" {
			summary.MissionCode = strings.TrimSpace(stringPtrOrEmpty(flights[f].MissionCode))
		}

		missions, missErr := h.mission.ListByFlightID(ctx, flights[f].ID)
		if missErr != nil {
			continue
		}

		for i := range missions {
			missionType := strings.ToUpper(strings.TrimSpace(missions[i].Type))
			if missionType != "" {
				if _, ok := missionTypeSeen[missionType]; !ok {
					missionTypeSeen[missionType] = struct{}{}
					summary.MissionType = append(summary.MissionType, missionType)
				}
			}
			if h.flightData == nil || len(missions[i].ID) != 16 {
				continue
			}
			fds, fdErr := h.flightData.ListByMissionID(ctx, missions[i].ID)
			if fdErr != nil {
				continue
			}
			summary.TotalFlight += len(fds)
			for j := range fds {
				summary.FlightDataCount++
				summary.FlightTimeSeconds += flightDataFlightTimeSeconds(&fds[j])

				fromLabel := flightDataLocationLabel(fromLocationRefFlightData(&fds[j]))
				if fromLabel != "" {
					route = appendRoutePoint(route, fromLabel)
				}
				toLabel := flightDataLocationLabel(toLocationRefFlightData(&fds[j]))
				if toLabel != "" {
					route = appendRoutePoint(route, toLabel)
				}
			}
		}
	}
	summary.Route = route
	return summary
}

func (h *ReserveAcHandler) loadMissionFlightData(ctx context.Context, row mission.Mission) (*flightdata.FlightData, bool) {
	hasFlightData := len(row.FlightDataID) == 16
	if h == nil || h.flightData == nil {
		return nil, hasFlightData
	}

	if len(row.ID) == 16 {
		if fd, err := h.flightData.GetByMissionID(ctx, row.ID); err == nil && fd != nil {
			return fd, true
		}
	}
	if len(row.FlightDataID) == 16 {
		if fd, err := h.flightData.GetByID(ctx, row.FlightDataID); err == nil && fd != nil {
			return fd, true
		}
	}
	return nil, hasFlightData
}

func appendRoutePoint(route []string, point string) []string {
	point = strings.TrimSpace(point)
	if point == "" {
		return route
	}
	if len(route) == 0 || route[len(route)-1] != point {
		return append(route, point)
	}
	return route
}

func flightDataLocationLabel(ref *responsedto.FlightDataLocationRef) string {
	if ref == nil {
		return ""
	}
	for _, candidate := range []string{ref.Label, ref.Code, ref.Name, ref.ID} {
		if trimmed := strings.TrimSpace(candidate); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func flightDataFlightTimeSeconds(row *flightdata.FlightData) int64 {
	if row == nil {
		return 0
	}
	if !row.FlightTakeOff.IsZero() && !row.FlightLanding.IsZero() && row.FlightLanding.After(row.FlightTakeOff) {
		return int64(row.FlightLanding.Sub(row.FlightTakeOff).Seconds())
	}
	if row.FlightDuration > 0 {
		return int64(row.FlightDuration.Seconds())
	}
	return 0
}

func toAfterFlightChecklistItems(items []shareddto.ReserveAcFileChecklistItemDTO) []rootdto.AfterFlightInspectionFileChecklistItem {
	if len(items) == 0 {
		return []rootdto.AfterFlightInspectionFileChecklistItem{}
	}
	out := make([]rootdto.AfterFlightInspectionFileChecklistItem, 0, len(items))
	for i := range items {
		out = append(out, rootdto.AfterFlightInspectionFileChecklistItem{
			HelicopterFileID: items[i].HelicopterFileID,
			FileName:         items[i].FileName,
			DownloadURL:      items[i].DownloadURL,
			IsDone:           items[i].IsDone,
		})
	}
	return out
}

func (h *ReserveAcHandler) calculateReserveAcStatus(summary *shareddto.ReserveAcInspectionSummary) string {
	if summary == nil {
		return reserveac.StatusPending
	}

	totalChecklist := summary.Before.Total + summary.Prepare.Total
	doneChecklist := summary.Before.Completed + summary.Prepare.Completed

	if totalChecklist > 0 {
		if doneChecklist == 0 {
			return reserveac.StatusPending
		}
		if doneChecklist < totalChecklist {
			return reserveac.StatusOnProgress
		}
		return reserveac.StatusApproved
	}

	// Fallback when no checklist templates exist: use inspection field presence as progress signal.
	if summary.Before.Fields != nil || summary.Prepare.PrepareFields != nil {
		return reserveac.StatusOnProgress
	}

	return reserveac.StatusPending
}

func (h *ReserveAcHandler) buildAircraftSummary(c *fiber.Ctx, aircraftID []byte) (*shareddto.ReserveAcAircraftSummary, error) {
	if h.aircraft == nil || len(aircraftID) != 16 {
		return nil, nil
	}
	row, err := h.aircraft.GetByID(c.UserContext(), aircraftID)
	if err != nil || row == nil {
		return nil, err
	}
	return &shareddto.ReserveAcAircraftSummary{
		ID:          idString(row.ID),
		Designation: strings.TrimSpace(row.Designation),
		Identifier:  strings.TrimSpace(row.Identifier),
		Type:        strings.TrimSpace(row.Type),
		AOG:         row.AirOnGround,
		IsActive:    row.IsActive,
	}, nil
}

func (h *ReserveAcHandler) upsertInspectionFileChecklistSection(
	c *fiber.Ctx,
	inspectionID []byte,
	helicopterID []byte,
	section string,
	payload []requestdto.ReserveAcFileChecklistItem,
	payloadPointer string,
	reserveAcID []byte,
) (bool, error) {
	templateFiles, err := h.heliFile.ListByHelicopterAndSection(c.UserContext(), helicopterID, section)
	if err != nil {
		if len(reserveAcID) == 16 {
			_ = h.svc.Delete(c.UserContext(), reserveAcID, actorUserID(c))
		}
		if err := writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}}); err != nil {
			return false, err
		}
		return false, nil
	}

	type requestedChecklist struct {
		fileID []byte
		isDone bool
	}
	requested := make([]requestedChecklist, 0, len(payload))
	for i := range payload {
		fileID, parseErr := uuidv7.ParseString(strings.TrimSpace(payload[i].HelicopterFileID))
		if parseErr != nil {
			if len(reserveAcID) == 16 {
				_ = h.svc.Delete(c.UserContext(), reserveAcID, actorUserID(c))
			}
			if err := writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "helicopter_file_id is invalid UUID",
				Source: &jsonapi.ErrorSource{Pointer: payloadPointer},
			}}); err != nil {
				return false, err
			}
			return false, nil
		}
		requested = append(requested, requestedChecklist{
			fileID: fileID,
			isDone: payload[i].IsDone,
		})
	}
	requestedByFileID := make(map[string]bool, len(requested))
	for i := range requested {
		requestedByFileID[string(requested[i].fileID)] = requested[i].isDone
	}

	existingRows, err := h.fileCk.ListByInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		if len(reserveAcID) == 16 {
			_ = h.svc.Delete(c.UserContext(), reserveAcID, actorUserID(c))
		}
		if err := writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: "an internal error occurred",
		}}); err != nil {
			return false, err
		}
		return false, nil
	}
	existingByFileID := make(map[string]*flightinspectionfilechecklist.FlightInspectionFileChecklist, len(existingRows))
	for i := range existingRows {
		fileID := string(existingRows[i].HelicopterFileID)
		existingByFileID[fileID] = &existingRows[i]
	}

	templateSet := make(map[string]struct{}, len(templateFiles))
	for i := range templateFiles {
		fileKey := string(templateFiles[i].ID)
		templateSet[fileKey] = struct{}{}

		current := existingByFileID[fileKey]
		targetDone, hasOverride := requestedByFileID[fileKey]

		if templateFiles[i].IsMandatory && !targetDone {
			if len(reserveAcID) == 16 {
				_ = h.svc.Delete(c.UserContext(), reserveAcID, actorUserID(c))
			}
			if err := writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "file_checklist must be fully checked",
				Source: &jsonapi.ErrorSource{Pointer: payloadPointer},
			}}); err != nil {
				return false, err
			}
			return false, nil
		}

		if current == nil {
			// New row defaults to false unless explicitly provided in payload.
			if !hasOverride {
				targetDone = false
			}
			newRow := &flightinspectionfilechecklist.FlightInspectionFileChecklist{
				FlightInspectionID: inspectionID,
				HelicopterFileID:   templateFiles[i].ID,
				IsDone:             targetDone,
				CreatedBy:          actorUserID(c),
				UpdatedBy:          actorUserID(c),
			}
			if err := h.fileCk.Create(c.UserContext(), newRow); err != nil {
				if len(reserveAcID) == 16 {
					_ = h.svc.Delete(c.UserContext(), reserveAcID, actorUserID(c))
				}
				if err := writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
					Status: "400",
					Title:  "Create failed",
					Detail: "an internal error occurred",
				}}); err != nil {
					return false, err
				}
				return false, nil
			}
			continue
		}

		// Partial update behavior: keep existing value if file is omitted.
		if !hasOverride {
			targetDone = current.IsDone
		}

		if current.IsDone != targetDone {
			current.IsDone = targetDone
			current.UpdatedBy = actorUserID(c)
			if err := h.fileCk.Update(c.UserContext(), current); err != nil {
				if len(reserveAcID) == 16 {
					_ = h.svc.Delete(c.UserContext(), reserveAcID, actorUserID(c))
				}
				if err := writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
					Status: "400",
					Title:  "Create failed",
					Detail: "an internal error occurred",
				}}); err != nil {
					return false, err
				}
				return false, nil
			}
		}
	}

	for i := range requested {
		if _, ok := templateSet[string(requested[i].fileID)]; ok {
			continue
		}
		if len(reserveAcID) == 16 {
			_ = h.svc.Delete(c.UserContext(), reserveAcID, actorUserID(c))
		}
		if err := writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "helicopter_file_id does not belong to selected helicopter/section",
			Source: &jsonapi.ErrorSource{Pointer: payloadPointer},
		}}); err != nil {
			return false, err
		}
		return false, nil
	}

	return true, nil
}

func (h *ReserveAcHandler) validateInspectionChecklistSectionComplete(
	c *fiber.Ctx,
	helicopterID []byte,
	section string,
	payload []requestdto.ReserveAcFileChecklistItem,
	payloadPointer string,
) (bool, error) {
	if h == nil || h.heliFile == nil {
		if err := writeReserveAcErrors(c, fiber.StatusInternalServerError, []jsonapi.ErrorObject{{
			Status: "500",
			Title:  "Validation failed",
			Detail: "helicopter file service is not configured",
		}}); err != nil {
			return false, err
		}
		return false, nil
	}

	templateFiles, err := h.heliFile.ListByHelicopterAndSection(c.UserContext(), helicopterID, section)
	if err != nil {
		if err := writeReserveAcErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Validation failed",
			Detail: "an internal error occurred",
		}}); err != nil {
			return false, err
		}
		return false, nil
	}

	templateSet := make(map[string]struct{}, len(templateFiles))
	for i := range templateFiles {
		templateSet[string(templateFiles[i].ID)] = struct{}{}
	}
	payloadByFileID := make(map[string]bool, len(payload))
	seen := make(map[string]struct{}, len(payload))
	for i := range payload {
		fileID, parseErr := uuidv7.ParseString(strings.TrimSpace(payload[i].HelicopterFileID))
		if parseErr != nil {
			if err := writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "helicopter_file_id is invalid UUID",
				Source: &jsonapi.ErrorSource{Pointer: payloadPointer},
			}}); err != nil {
				return false, err
			}
			return false, nil
		}
		key := string(fileID)
		if _, ok := templateSet[key]; !ok {
			if err := writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "helicopter_file_id does not belong to selected helicopter/section",
				Source: &jsonapi.ErrorSource{Pointer: payloadPointer},
			}}); err != nil {
				return false, err
			}
			return false, nil
		}
		if _, ok := seen[key]; ok {
			if err := writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "helicopter_file_id is duplicated",
				Source: &jsonapi.ErrorSource{Pointer: payloadPointer},
			}}); err != nil {
				return false, err
			}
			return false, nil
		}
		seen[key] = struct{}{}
		payloadByFileID[key] = payload[i].IsDone
	}

	for i := range templateFiles {
		fileKey := string(templateFiles[i].ID)
		done, ok := payloadByFileID[fileKey]
		if !ok {
			done = false
		}
		if templateFiles[i].IsMandatory && !done {
			if err := writeReserveAcErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "file_checklist must be fully checked",
				Source: &jsonapi.ErrorSource{Pointer: payloadPointer},
			}}); err != nil {
				return false, err
			}
			return false, nil
		}
	}
	return true, nil
}

func (h *ReserveAcHandler) deleteInspectionFileChecklistSection(
	c *fiber.Ctx,
	inspectionID []byte,
	helicopterID []byte,
	section string,
) error {
	templateFiles, err := h.heliFile.ListByHelicopterAndSection(c.UserContext(), helicopterID, section)
	if err != nil {
		return err
	}
	templateSet := make(map[string]struct{}, len(templateFiles))
	for i := range templateFiles {
		templateSet[string(templateFiles[i].ID)] = struct{}{}
	}

	existingRows, err := h.fileCk.ListByInspectionID(c.UserContext(), inspectionID)
	if err != nil {
		return err
	}
	for i := range existingRows {
		if _, ok := templateSet[string(existingRows[i].HelicopterFileID)]; !ok {
			continue
		}
		if err := h.fileCk.Delete(c.UserContext(), existingRows[i].ID); err != nil {
			return err
		}
	}
	return nil
}
