package handlers

import (
	"errors"
	"strings"
	"time"

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

	filemanager "wucher/internal/domain/file_manager"
	"wucher/internal/domain/operation"
	"wucher/internal/shared/pkg/uuidv7"
	"wucher/internal/transport/http/dto"
	"wucher/internal/transport/http/jsonapi"
	"wucher/internal/transport/http/response"
	"wucher/internal/transport/http/validators"
)

type HEMSOperationalDataHandler struct {
	svc      operation.HEMSOperationalDataService
	fileSvc  filemanager.Service
	validate *validators.Validator
}

func NewHEMSOperationalDataHandler(svc operation.HEMSOperationalDataService) *HEMSOperationalDataHandler {
	return &HEMSOperationalDataHandler{
		svc:      svc,
		validate: validators.New(),
	}
}

func (h *HEMSOperationalDataHandler) WithFileManagerService(fileSvc filemanager.Service) *HEMSOperationalDataHandler {
	h.fileSvc = fileSvc
	return h
}

// CreateHEMSOperationalData godoc
// @Summary Create HEMS operational data
// @Description Create a HEMS operational data record.
// @Tags Operation - HEMS Operational Data
// @Accept json
// @Produce json
// @Param request body dto.HEMSOperationalDataCreateRequest true "JSON:API HEMS operational data create request"
// @Success 201 {object} dto.HEMSOperationalDataResponse
// @Failure 422 {object} dto.ErrorResponse
// @Failure 400 {object} dto.ErrorResponse
// @Router /api/v1/hems-operational-data/create [post]
func (h *HEMSOperationalDataHandler) Create(c *fiber.Ctx) error {
	var req dto.HEMSOperationalDataCreateRequest
	if err := c.BodyParser(&req); err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: safeInternalDetail(),
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	attrs := req.Data.Attributes
	tm, err := time.Parse(time.RFC3339, strings.TrimSpace(attrs.Time))
	if err != nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "time must be in RFC3339 format",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/time"},
		}})
	}

	location := strings.TrimSpace(attrs.Location)
	if location == "" {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "location is required",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/location"},
		}})
	}

	row := &operation.HEMSOperationalData{
		Time:           tm,
		Location:       location,
		Postcode:       strings.TrimSpace(stringOrDefault(attrs.Postcode, "")),
		Notes:          strings.TrimSpace(stringOrDefault(attrs.Notes, "")),
		TerrainIndex:   strings.TrimSpace(stringOrDefault(attrs.TerrainIndex, "")),
		AdditionalInfo: strings.TrimSpace(stringOrDefault(attrs.AdditionalInfo, "")),
		CreatedBy:      actorUserID(c),
		UpdatedBy:      actorUserID(c),
	}

	if attrs.Darkness != nil {
		row.Darkness = *attrs.Darkness
	}
	if attrs.FalseInformation != nil {
		row.FalseInformation = *attrs.FalseInformation
	}
	if attrs.Fog != nil {
		row.Fog = *attrs.Fog
	}
	if attrs.Precipitation != nil {
		row.Precipitation = *attrs.Precipitation
	}
	if attrs.MountainUse != nil {
		row.MountainUse = *attrs.MountainUse
	}
	if attrs.Wind != nil {
		row.Wind = *attrs.Wind
	}
	if attrs.NFOSearch != nil {
		row.NFOSearch = *attrs.NFOSearch
	}
	if attrs.LandingSiteSearch != nil {
		row.LandingSiteSearch = *attrs.LandingSiteSearch
	}
	if attrs.Temperature != nil {
		row.Temperature = *attrs.Temperature
	}
	if attrs.Altitude != nil {
		row.Altitude = *attrs.Altitude
	}
	if attrs.RopeRecovery != nil {
		row.RopeRecovery = *attrs.RopeRecovery
	}
	if attrs.NightFlight != nil {
		row.NightFlight = *attrs.NightFlight
	}
	if attrs.InstrumentFlight != nil {
		row.InstrumentFlight = *attrs.InstrumentFlight
	}

	if attrs.LandID != nil {
		parsed, errObj := parseOptionalUUID(attrs.LandID, "/data/attributes/land_id")
		if errObj != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
		}
		row.LandID = parsed
	}
	if attrs.VocationID != nil {
		parsed, errObj := parseOptionalUUID(attrs.VocationID, "/data/attributes/vocation_id")
		if errObj != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
		}
		row.VocationID = parsed
	}
	seenForcePresent := make(map[string]struct{}, len(attrs.ForcePresentIDs))
	for i := range attrs.ForcePresentIDs {
		raw := strings.TrimSpace(attrs.ForcePresentIDs[i])
		if raw == "" {
			continue
		}
		if _, ok := seenForcePresent[raw]; ok {
			continue
		}
		id, err := uuidv7.ParseString(raw)
		if err != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "force_present_ids contains invalid UUID",
				Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/force_present_ids"},
			}})
		}
		seenForcePresent[raw] = struct{}{}
		row.ForcePresents = append(row.ForcePresents, operation.OperationalDataForcePresent{ForcePresentID: id})
	}
	if attrs.StateID != nil {
		parsed, errObj := parseOptionalUUID(attrs.StateID, "/data/attributes/state_id")
		if errObj != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
		}
		row.StateID = parsed
	}

	seen := make(map[string]struct{}, len(attrs.FileAttachmentIDs))
	for i := range attrs.FileAttachmentIDs {
		raw := strings.TrimSpace(attrs.FileAttachmentIDs[i])
		if raw == "" {
			continue
		}
		if _, ok := seen[raw]; ok {
			continue
		}
		attachmentID, err := uuidv7.ParseString(raw)
		if err != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "file_attachment_ids contains invalid UUID",
				Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/file_attachment_ids"},
			}})
		}
		seen[raw] = struct{}{}
		row.Files = append(row.Files, operation.OperationalFile{FileAttachmentID: attachmentID})
	}

	if err := h.svc.Create(c.UserContext(), row); err != nil {
		if status, errs, ok := mapOperationRelationError(err, map[string]relationFieldError{
			"(`land_id`)":                 {Pointer: "/data/attributes/land_id", Detail: "land_id references data that does not exist"},
			"(`vocation_id`)":             {Pointer: "/data/attributes/vocation_id", Detail: "vocation_id references data that does not exist"},
			"(`state_id`)":                {Pointer: "/data/attributes/state_id", Detail: "state_id references data that does not exist"},
			"(`force_present_id`)":        {Pointer: "/data/attributes/force_present_ids", Detail: "one or more force_present_ids reference data that does not exist"},
			"(`file_attachment_id`)":      {Pointer: "/data/attributes/file_attachment_ids", Detail: "one or more file_attachment_ids reference data that does not exist"},
			"references `lands`":          {Pointer: "/data/attributes/land_id", Detail: "land_id references data that does not exist"},
			"references `vocations`":      {Pointer: "/data/attributes/vocation_id", Detail: "vocation_id references data that does not exist"},
			"references `federal_states`": {Pointer: "/data/attributes/state_id", Detail: "state_id references data that does not exist"},
			"references `forces_present`": {Pointer: "/data/attributes/force_present_ids", Detail: "one or more force_present_ids reference data that does not exist"},
			"references `attachments`":    {Pointer: "/data/attributes/file_attachment_ids", Detail: "one or more file_attachment_ids reference data that does not exist"},
		}); ok {
			return response.WriteErrors(c, status, errs)
		}
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncAttachmentLifecycleSet(c.UserContext(), h.fileSvc, nil, operationalFileAttachmentIDs(row.Files), actorUserID(c)); err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: safeInternalDetail(),
		}})
	}

	created, err := h.svc.GetByID(c.UserContext(), row.ID)
	if err == nil && created != nil {
		return response.Write(c, fiber.StatusCreated, hemsOperationalDataResource(created))
	}
	return response.Write(c, fiber.StatusCreated, hemsOperationalDataResource(row))
}

// GetHEMSOperationalData godoc
// @Summary Get HEMS operational data by ID
// @Tags Operation - HEMS Operational Data
// @Produce json
// @Param uuid path string true "HEMS operational data UUID (UUIDv7)"
// @Success 200 {object} dto.HEMSOperationalDataResponse
// @Failure 422 {object} dto.ErrorResponse
// @Failure 404 {object} dto.ErrorResponse
// @Router /api/v1/hems-operational-data/get/{uuid} [get]
func (h *HEMSOperationalDataHandler) Get(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "uuid is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/path/uuid"},
		}})
	}
	row, err := h.svc.GetByID(c.UserContext(), id)
	if err != nil || row == nil {
		return response.WriteErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "hems operational data not found",
		}})
	}
	return response.Write(c, fiber.StatusOK, hemsOperationalDataResource(row))
}

// UpdateHEMSOperationalData godoc
// @Summary Update HEMS operational data (partial)
// @Tags Operation - HEMS Operational Data
// @Accept json
// @Produce json
// @Param uuid path string true "HEMS operational data UUID (UUIDv7)"
// @Param request body dto.HEMSOperationalDataUpdateRequest true "JSON:API HEMS operational data update request"
// @Success 200 {object} dto.HEMSOperationalDataResponse
// @Failure 422 {object} dto.ErrorResponse
// @Failure 404 {object} dto.ErrorResponse
// @Router /api/v1/hems-operational-data/update/{uuid} [patch]
func (h *HEMSOperationalDataHandler) Update(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "uuid is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/path/uuid"},
		}})
	}

	var req dto.HEMSOperationalDataUpdateRequest
	if err := c.BodyParser(&req); err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: safeInternalDetail(),
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	attrs := req.Data.Attributes
	if attrs.Time == nil && attrs.LandID == nil && attrs.VocationID == nil &&
		attrs.ForcePresentIDs == nil && attrs.StateID == nil && attrs.Location == nil &&
		attrs.Postcode == nil && attrs.Notes == nil && attrs.Darkness == nil &&
		attrs.FalseInformation == nil && attrs.Fog == nil && attrs.Precipitation == nil &&
		attrs.MountainUse == nil && attrs.Wind == nil && attrs.NFOSearch == nil &&
		attrs.LandingSiteSearch == nil && attrs.Temperature == nil && attrs.Altitude == nil &&
		attrs.TerrainIndex == nil && attrs.RopeRecovery == nil && attrs.NightFlight == nil &&
		attrs.InstrumentFlight == nil && attrs.AdditionalInfo == nil && attrs.FileAttachmentIDs == nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "at least one attribute must be provided",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes"},
		}})
	}

	existing, err := h.svc.GetByID(c.UserContext(), id)
	if err != nil || existing == nil {
		return response.WriteErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "hems operational data not found",
		}})
	}
	prevAttachmentIDs := operationalFileAttachmentIDs(existing.Files)

	if attrs.Time != nil {
		tm, err := time.Parse(time.RFC3339, strings.TrimSpace(*attrs.Time))
		if err != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "time must be in RFC3339 format",
				Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/time"},
			}})
		}
		existing.Time = tm
	}
	if attrs.LandID != nil {
		parsed, errObj := parseOptionalUUID(attrs.LandID, "/data/attributes/land_id")
		if errObj != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
		}
		existing.LandID = parsed
	}
	if attrs.VocationID != nil {
		parsed, errObj := parseOptionalUUID(attrs.VocationID, "/data/attributes/vocation_id")
		if errObj != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
		}
		existing.VocationID = parsed
	}
	if attrs.ForcePresentIDs != nil {
		existing.ForcePresents = existing.ForcePresents[:0]
		seen := make(map[string]struct{}, len(*attrs.ForcePresentIDs))
		for i := range *attrs.ForcePresentIDs {
			raw := strings.TrimSpace((*attrs.ForcePresentIDs)[i])
			if raw == "" {
				continue
			}
			if _, ok := seen[raw]; ok {
				continue
			}
			id, err := uuidv7.ParseString(raw)
			if err != nil {
				return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
					Status: "422",
					Title:  "Validation error",
					Detail: "force_present_ids contains invalid UUID",
					Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/force_present_ids"},
				}})
			}
			seen[raw] = struct{}{}
			existing.ForcePresents = append(existing.ForcePresents, operation.OperationalDataForcePresent{ForcePresentID: id})
		}
	}
	if attrs.StateID != nil {
		parsed, errObj := parseOptionalUUID(attrs.StateID, "/data/attributes/state_id")
		if errObj != nil {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
		}
		existing.StateID = parsed
	}
	if attrs.Location != nil {
		v := strings.TrimSpace(*attrs.Location)
		if v == "" {
			return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "location cannot be empty",
				Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/location"},
			}})
		}
		existing.Location = v
	}
	if attrs.Postcode != nil {
		existing.Postcode = strings.TrimSpace(*attrs.Postcode)
	}
	if attrs.Notes != nil {
		existing.Notes = strings.TrimSpace(*attrs.Notes)
	}
	if attrs.Darkness != nil {
		existing.Darkness = *attrs.Darkness
	}
	if attrs.FalseInformation != nil {
		existing.FalseInformation = *attrs.FalseInformation
	}
	if attrs.Fog != nil {
		existing.Fog = *attrs.Fog
	}
	if attrs.Precipitation != nil {
		existing.Precipitation = *attrs.Precipitation
	}
	if attrs.MountainUse != nil {
		existing.MountainUse = *attrs.MountainUse
	}
	if attrs.Wind != nil {
		existing.Wind = *attrs.Wind
	}
	if attrs.NFOSearch != nil {
		existing.NFOSearch = *attrs.NFOSearch
	}
	if attrs.LandingSiteSearch != nil {
		existing.LandingSiteSearch = *attrs.LandingSiteSearch
	}
	if attrs.Temperature != nil {
		existing.Temperature = *attrs.Temperature
	}
	if attrs.Altitude != nil {
		existing.Altitude = *attrs.Altitude
	}
	if attrs.TerrainIndex != nil {
		existing.TerrainIndex = strings.TrimSpace(*attrs.TerrainIndex)
	}
	if attrs.RopeRecovery != nil {
		existing.RopeRecovery = *attrs.RopeRecovery
	}
	if attrs.NightFlight != nil {
		existing.NightFlight = *attrs.NightFlight
	}
	if attrs.InstrumentFlight != nil {
		existing.InstrumentFlight = *attrs.InstrumentFlight
	}
	if attrs.AdditionalInfo != nil {
		existing.AdditionalInfo = strings.TrimSpace(*attrs.AdditionalInfo)
	}

	if attrs.FileAttachmentIDs != nil {
		existing.Files = existing.Files[:0]
		seen := make(map[string]struct{}, len(*attrs.FileAttachmentIDs))
		for i := range *attrs.FileAttachmentIDs {
			raw := strings.TrimSpace((*attrs.FileAttachmentIDs)[i])
			if raw == "" {
				continue
			}
			if _, ok := seen[raw]; ok {
				continue
			}
			attachmentID, err := uuidv7.ParseString(raw)
			if err != nil {
				return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
					Status: "422",
					Title:  "Validation error",
					Detail: "file_attachment_ids contains invalid UUID",
					Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/file_attachment_ids"},
				}})
			}
			seen[raw] = struct{}{}
			existing.Files = append(existing.Files, operation.OperationalFile{FileAttachmentID: attachmentID})
		}
	}

	existing.UpdatedBy = actorUserID(c)
	if err := h.svc.Update(c.UserContext(), existing); err != nil {
		if status, errs, ok := mapOperationRelationError(err, map[string]relationFieldError{
			"(`land_id`)":                 {Pointer: "/data/attributes/land_id", Detail: "land_id references data that does not exist"},
			"(`vocation_id`)":             {Pointer: "/data/attributes/vocation_id", Detail: "vocation_id references data that does not exist"},
			"(`state_id`)":                {Pointer: "/data/attributes/state_id", Detail: "state_id references data that does not exist"},
			"(`force_present_id`)":        {Pointer: "/data/attributes/force_present_ids", Detail: "one or more force_present_ids reference data that does not exist"},
			"(`file_attachment_id`)":      {Pointer: "/data/attributes/file_attachment_ids", Detail: "one or more file_attachment_ids reference data that does not exist"},
			"references `lands`":          {Pointer: "/data/attributes/land_id", Detail: "land_id references data that does not exist"},
			"references `vocations`":      {Pointer: "/data/attributes/vocation_id", Detail: "vocation_id references data that does not exist"},
			"references `federal_states`": {Pointer: "/data/attributes/state_id", Detail: "state_id references data that does not exist"},
			"references `forces_present`": {Pointer: "/data/attributes/force_present_ids", Detail: "one or more force_present_ids reference data that does not exist"},
			"references `attachments`":    {Pointer: "/data/attributes/file_attachment_ids", Detail: "one or more file_attachment_ids reference data that does not exist"},
		}); ok {
			return response.WriteErrors(c, status, errs)
		}
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncAttachmentLifecycleSet(c.UserContext(), h.fileSvc, prevAttachmentIDs, operationalFileAttachmentIDs(existing.Files), actorUserID(c)); err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: safeInternalDetail(),
		}})
	}
	updated, err := h.svc.GetByID(c.UserContext(), existing.ID)
	if err == nil && updated != nil {
		return response.Write(c, fiber.StatusOK, hemsOperationalDataResource(updated))
	}
	return response.Write(c, fiber.StatusOK, hemsOperationalDataResource(existing))
}

// DeleteHEMSOperationalData godoc
// @Summary Delete HEMS operational data
// @Tags Operation - HEMS Operational Data
// @Produce json
// @Param uuid path string true "HEMS operational data UUID (UUIDv7)"
// @Success 200 {object} dto.GenericDeleteResponse
// @Failure 422 {object} dto.ErrorResponse
// @Failure 404 {object} dto.ErrorResponse
// @Failure 400 {object} dto.ErrorResponse
// @Router /api/v1/hems-operational-data/delete/{uuid} [delete]
func (h *HEMSOperationalDataHandler) Delete(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "uuid is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/path/uuid"},
		}})
	}
	existing, _ := h.svc.GetByID(c.UserContext(), id)
	var prevAttachmentIDs [][]byte
	if existing != nil {
		prevAttachmentIDs = operationalFileAttachmentIDs(existing.Files)
	}
	if err := h.svc.Delete(c.UserContext(), id, actorUserID(c)); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return response.WriteErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
				Status: "404",
				Title:  "Not found",
				Detail: "hems operational data not found",
			}})
		}
		if status, errs, ok := mapOperationRelationError(err, nil); ok {
			return response.WriteErrors(c, status, errs)
		}
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncAttachmentLifecycleSet(c.UserContext(), h.fileSvc, prevAttachmentIDs, nil, actorUserID(c)); err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: safeInternalDetail(),
		}})
	}
	return response.Write(c, fiber.StatusOK, jsonapi.Resource{
		Type: "hems_operational_data_delete",
		Attributes: map[string]any{
			"deleted": true,
		},
	})
}

func operationalFileAttachmentIDs(files []operation.OperationalFile) [][]byte {
	out := make([][]byte, 0, len(files))
	for i := range files {
		if len(files[i].FileAttachmentID) != 16 {
			continue
		}
		out = append(out, append([]byte(nil), files[i].FileAttachmentID...))
	}
	return out
}

// ListHEMSOperationalData godoc
// @Summary List HEMS operational data
// @Tags Operation - HEMS Operational Data
// @Produce json
// @Param filter[search] query string false "Search by location/notes/additional_info/terrain_index"
// @Param page[number] query int false "Page number (default 1)"
// @Param page[size] query int false "Page size (default 20, max 100)"
// @Param sort query string false "Sort order"
// @Success 200 {object} dto.HEMSOperationalDataListResponse
// @Router /api/v1/hems-operational-data/get-all [get]
func (h *HEMSOperationalDataHandler) List(c *fiber.Ctx) error {
	filter := c.Query("filter[search]")
	if filter == "" {
		filter = c.Query("search")
	}
	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
	sort := c.Query("sort")

	rows, total, err := h.svc.List(c.UserContext(), filter, sort, 0, 0)
	if err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "List failed",
			Detail: safeInternalDetail(),
		}})
	}
	data := make([]dto.HEMSOperationalDataResource, 0, len(rows))
	for i := range rows {
		data = append(data, hemsOperationalDataResource(&rows[i]).Data)
	}
	meta := map[string]any{
		"page_number": pageNumber,
		"page_size":   pageSize,
		"total":       total,
	}
	return response.WriteWithMeta(c, fiber.StatusOK, data, meta)
}

// ListHEMSOperationalDataDatatable godoc
// @Summary List HEMS operational data (datatable)
// @Tags Operation - HEMS Operational Data
// @Produce json
// @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 (same as size)"
// @Param draw query int false "Draw counter (optional)"
// @Param search query string false "Search term"
// @Success 200 {object} dto.HEMSOperationalDataDataTableResponse
// @Router /api/v1/hems-operational-data/get-all/dt [get]
func (h *HEMSOperationalDataHandler) ListDatatable(c *fiber.Ctx) error {
	pageNumber := parseIntDefault(c.Query("page"), 1)
	if pageNumber < 1 {
		pageNumber = 1
	}
	length := parseIntDefault(c.Query("limit"), 0)
	if length <= 0 {
		length = parseIntDefault(c.Query("size"), 20)
	}
	if length > 100 {
		length = 100
	}
	if length < 1 {
		length = 20
	}
	start := (pageNumber - 1) * length
	draw := parseIntDefault(c.Query("draw"), pageNumber)
	search := c.Query("search")

	rows, total, err := h.svc.List(c.UserContext(), search, "", length, start)
	if err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "List failed",
			Detail: safeInternalDetail(),
		}})
	}
	data := make([]dto.HEMSOperationalDataResource, 0, len(rows))
	for i := range rows {
		data = append(data, hemsOperationalDataResource(&rows[i]).Data)
	}
	meta := map[string]any{
		"draw":             draw,
		"records_total":    total,
		"records_filtered": total,
	}
	return response.WriteWithMeta(c, fiber.StatusOK, data, meta)
}

func hemsOperationalDataResource(row *operation.HEMSOperationalData) dto.HEMSOperationalDataResponse {
	if row == nil {
		return dto.HEMSOperationalDataResponse{Data: dto.HEMSOperationalDataResource{Type: "hems_operational_data"}}
	}

	id, _ := uuidv7.BytesToString(row.ID)
	landID, _ := uuidv7.BytesToString(row.LandID)
	vocationID, _ := uuidv7.BytesToString(row.VocationID)
	stateID, _ := uuidv7.BytesToString(row.StateID)
	createdBy, _ := uuidv7.BytesToString(row.CreatedBy)
	updatedBy, _ := uuidv7.BytesToString(row.UpdatedBy)
	deletedBy, _ := uuidv7.BytesToString(row.DeletedBy)

	files := make([]dto.HEMSOperationalDataFileAttributes, 0, len(row.Files))
	for i := range row.Files {
		fileID, _ := uuidv7.BytesToString(row.Files[i].ID)
		attachmentID, _ := uuidv7.BytesToString(row.Files[i].FileAttachmentID)
		files = append(files, dto.HEMSOperationalDataFileAttributes{
			ID:               fileID,
			FileAttachmentID: attachmentID,
		})
	}
	forcePresentIDs := make([]string, 0, len(row.ForcePresents))
	for i := range row.ForcePresents {
		forcePresentIDs = append(forcePresentIDs, uuidStringOrEmpty(row.ForcePresents[i].ForcePresentID))
	}

	attrs := dto.HEMSOperationalDataAttributes{
		Time:              row.Time.UTC().Format(time.RFC3339),
		LandID:            landID,
		VocationID:        vocationID,
		ForcePresentIDs:   forcePresentIDs,
		StateID:           stateID,
		Location:          row.Location,
		Postcode:          row.Postcode,
		Notes:             row.Notes,
		Darkness:          row.Darkness,
		FalseInformation:  row.FalseInformation,
		Fog:               row.Fog,
		Precipitation:     row.Precipitation,
		MountainUse:       row.MountainUse,
		Wind:              row.Wind,
		NFOSearch:         row.NFOSearch,
		LandingSiteSearch: row.LandingSiteSearch,
		Temperature:       row.Temperature,
		Altitude:          row.Altitude,
		TerrainIndex:      row.TerrainIndex,
		RopeRecovery:      row.RopeRecovery,
		NightFlight:       row.NightFlight,
		InstrumentFlight:  row.InstrumentFlight,
		AdditionalInfo:    row.AdditionalInfo,
		Files:             files,
		CreatedBy:         createdBy,
		UpdatedBy:         updatedBy,
		DeletedBy:         deletedBy,
	}
	if !row.CreatedAt.IsZero() {
		attrs.CreatedAt = row.CreatedAt.UTC().Format(time.RFC3339)
	}
	if !row.UpdatedAt.IsZero() {
		attrs.UpdatedAt = row.UpdatedAt.UTC().Format(time.RFC3339)
	}
	if row.DeletedAt != nil {
		attrs.DeletedAt = row.DeletedAt.UTC().Format(time.RFC3339)
	}

	return dto.HEMSOperationalDataResponse{
		Data: dto.HEMSOperationalDataResource{
			Type:       "hems_operational_data",
			ID:         id,
			Attributes: attrs,
		},
	}
}
