package handlers

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/gofiber/fiber/v2"

	airrescuechecklist "wucher/internal/domain/air_rescue_checklist"
	basedomain "wucher/internal/domain/base"
	"wucher/internal/shared/pkg/apperrorsx"
	"wucher/internal/shared/pkg/uuidv7"
	requestdto "wucher/internal/transport/http/dto/request"
	responsedto "wucher/internal/transport/http/dto/response"
	"wucher/internal/transport/http/jsonapi"
	"wucher/internal/transport/http/response"
	"wucher/internal/transport/http/validators"
)

type AirRescuerChecklistHandler struct {
	svc      airrescuechecklist.Service
	validate *validators.Validator
}

func NewAirRescuerChecklistHandler(svc airrescuechecklist.Service) *AirRescuerChecklistHandler {
	return &AirRescuerChecklistHandler{svc: svc, validate: validators.New()}
}

// Create godoc
// @Summary Create air rescuer checklist (bulk)
// @Description Create one or more checklist headers for a single base.
// @Description Request uses `base_id`.
// @Description Each checklist can be created with or without initial `items`.
// @Description `scope_code` accepts persisted codes and English aliases. Stored codes remain: `TA`, `MO`, `DI`, `MI`, `DO`, `FR`, `SA`, `SO`, `M10`, `M15`, `M20`, `M25`, `DS`.
// @Description Example aliases: `DAILY` -> `TA`, `TU/TUE/TUESDAY` -> `DI`, `WE/WED/WEDNESDAY` -> `MI`, `TH/THU/THURSDAY` -> `DO`, `SU/SUN/SUNDAY` -> `SO`, `DEADLINE` -> `DS`.
// @Description `CAT` is a virtual overview tab and is not allowed for create/update payloads.
// @Description Duplicate `position` is allowed for both checklist headers and checklist items.
// @Description Display order is stable: checklist headers `position ASC, title ASC`; checklist_items `position ASC, name ASC`.
// @Description Request is transactional: if one checklist/item fails, the whole batch is rolled back.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param request body requestdto.AirRescuerChecklistCreateRequest true "Bulk checklist create payload"
// @Success 201 {object} responsedto.AirRescuerChecklistBulkResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/create [post]
func (h *AirRescuerChecklistHandler) Create(c *fiber.Ctx) error {
	var req requestdto.AirRescuerChecklistCreateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeAirRescuerChecklistErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: safeInternalDetail(),
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	baseID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.BaseID))
	if err != nil {
		return validationError(c, "/data/attributes/base_id", "base_id is invalid UUID")
	}

	baseRows, err := h.svc.ListHEMSBases(c.UserContext(), airrescuechecklist.HEMSBaseFilter{HEMSBaseID: baseID})
	if err != nil {
		status, errObj := buildServiceErrorObject("Create failed", err, "")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}
	if len(baseRows) == 0 {
		return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "base not found",
		}})
	}

	rows := make([]airrescuechecklist.ChecklistCreateInput, 0, len(req.Data.Attributes.Checklists))
	actor := actorUserID(c)
	for i := range req.Data.Attributes.Checklists {
		scopeCode := airrescuechecklist.CanonicalizeAirRescuerChecklistScope(req.Data.Attributes.Checklists[i].ScopeCode)
		if scopeCode == "" {
			return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/scope_code", i), "scope_code is required")
		}
		if !airrescuechecklist.IsAirRescuerChecklistPersistedScope(scopeCode) {
			return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/scope_code", i), "scope_code is invalid")
		}

		title := strings.TrimSpace(req.Data.Attributes.Checklists[i].Title)
		if title == "" {
			return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/title", i), "title is required")
		}

		position := checklistPositionOrZero(req.Data.Attributes.Checklists[i].Position)

		items := make([]airrescuechecklist.AirRescuerChecklistItem, 0, len(req.Data.Attributes.Checklists[i].Items))
		for j := range req.Data.Attributes.Checklists[i].Items {
			itemName := strings.TrimSpace(req.Data.Attributes.Checklists[i].Items[j].Name)
			if itemName == "" {
				return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/items/%d/name", i, j), "name is required")
			}
			items = append(items, airrescuechecklist.AirRescuerChecklistItem{
				Name:      itemName,
				Position:  checklistPositionOrZero(req.Data.Attributes.Checklists[i].Items[j].Position),
				CreatedBy: actor,
				UpdatedBy: actor,
			})
		}

		rows = append(rows, airrescuechecklist.ChecklistCreateInput{
			Checklist: airrescuechecklist.AirRescuerChecklist{
				HEMSBaseID: baseID,
				ScopeCode:  scopeCode,
				Title:      title,
				Position:   position,
				CreatedBy:  actor,
				UpdatedBy:  actor,
			},
			Items: items,
		})
	}

	if err := h.svc.CreateBulk(c.UserContext(), rows); err != nil {
		status, errObj := buildServiceErrorObject("Create failed", err, "")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

	data := make([]responsedto.AirRescuerChecklistResource, 0, len(rows))
	for i := range rows {
		resource, found, err := h.loadChecklistResource(c.UserContext(), rows[i].Checklist.ID)
		if err != nil {
			status, errObj := buildServiceErrorObject("Create failed", err, "")
			return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
		}
		if found {
			data = append(data, resource)
		}
	}

	return response.Write(c, fiber.StatusCreated, data)
}

// Get godoc
// @Summary Get air rescuer checklist by UUID
// @Description Return one checklist detail by UUID.
// @Description Includes checklist header, base info, and `items` (`checklist_items`).
// @Tags Air Rescuer Checklist
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Success 200 {object} responsedto.AirRescuerChecklistResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/get/{uuid} [get]
func (h *AirRescuerChecklistHandler) Get(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return validationError(c, "/path/uuid", "uuid is invalid UUID")
	}
	return h.respondChecklistByID(c, fiber.StatusOK, id)
}

// Update godoc
// @Summary Update air rescuer checklist header and append new items
// @Description Update checklist header fields (`scope_code`, `title`, `position`) and/or append `new_items`.
// @Description `new_items` is append-only. Existing items are not updated/deleted by this endpoint.
// @Description Item update/delete lifecycle must use dedicated item endpoints.
// @Description `scope_code` accepts persisted codes and English aliases, but `CAT` (or `OVERVIEW`) is not allowed for update payload.
// @Description Duplicate `position` is allowed.
// @Description Request is transactional.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Param request body requestdto.AirRescuerChecklistUpdateRequest true "Checklist update payload"
// @Success 200 {object} responsedto.AirRescuerChecklistResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/update/{uuid} [patch]
func (h *AirRescuerChecklistHandler) Update(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return validationError(c, "/path/uuid", "uuid is invalid UUID")
	}

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

	if strings.TrimSpace(req.Data.ID) != "" {
		reqID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.ID))
		if err != nil {
			return validationError(c, "/data/id", "id is invalid UUID")
		}
		if !bytesEqualUUID(reqID, id) {
			return validationError(c, "/data/id", "id does not match path uuid")
		}
	}

	existing, err := h.svc.GetChecklistByID(c.UserContext(), id)
	if err != nil {
		status, errObj := buildServiceErrorObject("Update failed", err, "")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}
	if existing == nil {
		return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "air rescuer checklist not found",
		}})
	}

	attrs := req.Data.Attributes
	hasHeaderUpdate := attrs.ScopeCode != nil || attrs.Title != nil || attrs.Position != nil
	if attrs.NewItems != nil && len(attrs.NewItems) == 0 {
		return validationError(c, "/data/attributes/new_items", "new_items must contain at least 1 item")
	}
	hasNewItems := len(attrs.NewItems) > 0
	if !hasHeaderUpdate && !hasNewItems {
		return validationError(c, "/data/attributes", "at least one attribute must be provided")
	}

	actor := actorUserID(c)

	var row *airrescuechecklist.AirRescuerChecklist
	if hasHeaderUpdate {
		scopeCode := airrescuechecklist.CanonicalizeAirRescuerChecklistScope(existing.ScopeCode)
		if attrs.ScopeCode != nil {
			scopeCode = airrescuechecklist.CanonicalizeAirRescuerChecklistScope(*attrs.ScopeCode)
			if scopeCode == "" {
				return validationError(c, "/data/attributes/scope_code", "scope_code is required")
			}
			if !airrescuechecklist.IsAirRescuerChecklistPersistedScope(scopeCode) {
				return validationError(c, "/data/attributes/scope_code", "scope_code is invalid")
			}
		}

		title := strings.TrimSpace(existing.Title)
		if attrs.Title != nil {
			title = strings.TrimSpace(*attrs.Title)
			if title == "" {
				return validationError(c, "/data/attributes/title", "title is required")
			}
		}

		position := existing.Position
		if attrs.Position != nil {
			position = checklistPositionOrDefault(attrs.Position)
		}

		row = &airrescuechecklist.AirRescuerChecklist{
			ID:        id,
			ScopeCode: scopeCode,
			Title:     title,
			Position:  position,
			UpdatedBy: actor,
		}
	}

	newItems := make([]airrescuechecklist.AirRescuerChecklistItem, 0, len(attrs.NewItems))
	for i := range attrs.NewItems {
		name := strings.TrimSpace(attrs.NewItems[i].Name)
		if name == "" {
			return validationError(c, fmt.Sprintf("/data/attributes/new_items/%d/name", i), "name is required")
		}
		newItems = append(newItems, airrescuechecklist.AirRescuerChecklistItem{
			ChecklistID: id,
			Name:        name,
			Position:    checklistPositionOrZero(attrs.NewItems[i].Position),
			CreatedBy:   actor,
			UpdatedBy:   actor,
		})
	}

	if err := h.svc.UpdateChecklistWithNewItems(c.UserContext(), row, newItems); err != nil {
		status, errObj := buildServiceErrorObject("Update failed", err, "air rescuer checklist not found")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

	return h.respondChecklistByID(c, fiber.StatusOK, id)
}

// Delete godoc
// @Summary Delete air rescuer checklist
// @Description Delete one checklist header by UUID.
// @Description Related checklist items are removed together following current implementation behavior.
// @Tags Air Rescuer Checklist
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Success 200 {object} responsedto.AirRescuerChecklistGroupedListResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/delete/{uuid} [delete]
func (h *AirRescuerChecklistHandler) Delete(c *fiber.Ctx) error {
	id, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return validationError(c, "/path/uuid", "uuid is invalid UUID")
	}
	if err := h.svc.DeleteChecklist(c.UserContext(), id, actorUserID(c)); err != nil {
		status, errObj := buildServiceErrorObject("Delete failed", err, "air rescuer checklist not found")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}
	return response.Write(c, fiber.StatusOK, jsonapi.Resource{Type: "air_rescuer_checklist_delete", Attributes: map[string]any{"deleted": true}})
}

// List godoc
// @Summary List air rescuer checklist grouped by base
// @Description Grouped by base and returned in `data[].attributes.bases`.
// @Description Response includes `tabs[]` with default tab order: `CAT`, `TA`, `MO`, `DI`, `MI`, `DO`, `FR`, `SA`, `SO`, `M10`, `M15`, `M20`, `M25`, `DS`.
// @Description `CAT` is a virtual overview tab (not persisted in DB).
// @Description `CAT.groups[]` only includes non-empty persisted scopes and does not include `checklist_items`.
// @Description Normal tabs are always present. Empty normal tabs are explicitly returned as `checklists: []`.
// @Description Scope display names: `CAT=Category`, `TA=Daily`, `MO=Monday`, `DI=Tuesday`, `MI=Wednesday`, `DO=Thursday`, `FR=Friday`, `SA=Saturday`, `SO=Sunday`, `M10=M10`, `M15=M15`, `M20=M20`, `M25=M25`, `DS=Deadline`.
// @Description Duplicate `position` is allowed. Display order is stable: checklist headers `position ASC, title ASC`; checklist_items `position ASC, name ASC`.
// @Description Optional filters: `base_id`, `category_type` (`regular|hems`), `base_name`, `base_abbreviation`, `scope_code` (`CAT`/`OVERVIEW` or persisted scope / aliases).
// @Tags Air Rescuer Checklist
// @Produce json
// @Param base_id query string false "Filter by base UUID"
// @Param category_type query string false "Filter by base category" Enums(regular,hems)
// @Param base_name query string false "Filter by base name (contains, case-insensitive)"
// @Param base_abbreviation query string false "Filter by base abbreviation (contains, case-insensitive)"
// @Param scope_code query string false "Filter by scope code. Allowed canonical: CAT, TA, MO, DI, MI, DO, FR, SA, SO, M10, M15, M20, M25, DS. English aliases are also accepted."
// @Success 200 {object} responsedto.AirRescuerChecklistGroupedDataTableResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/get-all [get]
func (h *AirRescuerChecklistHandler) List(c *fiber.Ctx) error {
	headerFilter, baseFilter, requestedScope, _ := parseAirRescuerChecklistFilters(c)
	if c.Response().StatusCode() >= fiber.StatusBadRequest {
		return nil
	}

	groups, err := h.loadChecklistGroups(c.UserContext(), headerFilter, baseFilter, requestedScope)
	if err != nil {
		status, errObj := buildServiceErrorObject("List failed", err, "")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}
	meta := map[string]any{"total_bases": len(groups)}
	return response.WriteWithMeta(c, fiber.StatusOK, groups, meta)
}

// ListDatatable godoc
// @Summary List air rescuer checklist grouped by base (datatable)
// @Description Datatable variant for grouped air rescuer checklist data.
// @Description Semantic data is the same as `get-all`: grouped by base with `tabs[]`, virtual `CAT`, and normal tabs always present.
// @Description `CAT.groups[]` only includes non-empty persisted scopes and does not include `checklist_items`.
// @Description Empty normal tabs are explicitly returned as `checklists: []`.
// @Description Scope display names: `CAT=Category`, `TA=Daily`, `MO=Monday`, `DI=Tuesday`, `MI=Wednesday`, `DO=Thursday`, `FR=Friday`, `SA=Saturday`, `SO=Sunday`, `M10=M10`, `M15=M15`, `M20=M20`, `M25=M25`, `DS=Deadline`.
// @Description Duplicate `position` is allowed. Display order is stable: checklist headers `position ASC, title ASC`; checklist_items `position ASC, name ASC`.
// @Description Optional filters: `base_id`, `category_type` (`regular|hems`), `base_name`, `base_abbreviation`, `scope_code`.
// @Description Pagination/query controls: `page`, `size`, `limit`, `draw`.
// @Tags Air Rescuer Checklist
// @Produce json
// @Param base_id query string false "Filter by base UUID"
// @Param category_type query string false "Filter by base category" Enums(regular,hems)
// @Param base_name query string false "Filter by base name (contains, case-insensitive)"
// @Param base_abbreviation query string false "Filter by base abbreviation (contains, case-insensitive)"
// @Param scope_code query string false "Filter by scope code. Allowed canonical: CAT, TA, MO, DI, MI, DO, FR, SA, SO, M10, M15, M20, M25, DS. English aliases are also accepted."
// @Param page query int false "Page number (default 1)"
// @Param size query int false "Page size (default 20)"
// @Param limit query int false "Page size override (max 100)"
// @Param draw query int false "Datatable draw counter"
// @Success 200 {object} responsedto.AirRescuerChecklistGroupedDataTableResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/get-all/dt [get]
func (h *AirRescuerChecklistHandler) ListDatatable(c *fiber.Ctx) error {
	headerFilter, baseFilter, requestedScope, _ := parseAirRescuerChecklistFilters(c)
	if c.Response().StatusCode() >= fiber.StatusBadRequest {
		return nil
	}

	groups, err := h.loadChecklistGroups(c.UserContext(), headerFilter, baseFilter, requestedScope)
	if err != nil {
		status, errObj := buildServiceErrorObject("List failed", err, "")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

	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)

	pagedGroups, total := paginateAirRescuerChecklistGroups(groups, start, length)
	meta := map[string]any{"draw": draw, "records_total": total, "records_filtered": total}
	return response.WriteWithMeta(c, fiber.StatusOK, pagedGroups, meta)
}

// CreateItem godoc
// @Summary Create checklist items (bulk)
// @Description Create one or more items for an existing checklist.
// @Description Parent checklist is taken from path `uuid`.
// @Description Duplicate `position` is allowed. Display order for checklist_items is `position ASC, name ASC`.
// @Description Request is transactional: if one item fails, all item inserts in the batch are rolled back.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Param request body requestdto.AirRescuerChecklistItemCreateRequest true "Bulk checklist item create payload"
// @Success 201 {object} responsedto.AirRescuerChecklistItemBulkResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/{uuid}/items/create [post]
func (h *AirRescuerChecklistHandler) CreateItem(c *fiber.Ctx) error {
	checklistID, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return validationError(c, "/path/uuid", "uuid is invalid UUID")
	}

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

	actor := actorUserID(c)
	rows := make([]airrescuechecklist.AirRescuerChecklistItem, 0, len(req.Data.Attributes.Items))
	for i := range req.Data.Attributes.Items {
		name := strings.TrimSpace(req.Data.Attributes.Items[i].Name)
		if name == "" {
			return validationError(c, fmt.Sprintf("/data/attributes/items/%d/name", i), "name is required")
		}
		rows = append(rows, airrescuechecklist.AirRescuerChecklistItem{
			ChecklistID: checklistID,
			Name:        name,
			Position:    checklistPositionOrZero(req.Data.Attributes.Items[i].Position),
			CreatedBy:   actor,
			UpdatedBy:   actor,
		})
	}

	if err := h.svc.CreateChecklistItems(c.UserContext(), checklistID, rows); err != nil {
		status, errObj := buildServiceErrorObject("Create failed", err, "air rescuer checklist not found")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

	data := make([]responsedto.AirRescuerChecklistItemData, 0, len(rows))
	for i := range rows {
		created, err := h.svc.GetChecklistItemByID(c.UserContext(), rows[i].ID)
		if err != nil {
			status, errObj := buildServiceErrorObject("Create failed", err, "")
			return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
		}
		if created == nil {
			data = append(data, responsedto.AirRescuerChecklistItemData{
				Type: "air_rescuer_checklist_item",
				ID:   idString(rows[i].ID),
				Attributes: responsedto.AirRescuerChecklistItemAttributes{
					ChecklistID: idString(checklistID),
					Name:        rows[i].Name,
					Position:    rows[i].Position,
				},
			})
			continue
		}
		data = append(data, airRescuerChecklistItemResponseData(*created))
	}

	return response.Write(c, fiber.StatusCreated, data)
}

// ReorderItems godoc
// @Summary Reorder checklist items in one request
// @Description Reorder one or more checklist items for the checklist in path `uuid`.
// @Description Use this endpoint to swap or reorder positions with a single PIN validation.
// @Description Duplicate `position` values remain allowed by domain rules.
// @Description Request is transactional: if one item fails, all position updates are rolled back.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Param request body requestdto.AirRescuerChecklistItemReorderRequest true "Checklist item reorder payload"
// @Success 200 {object} responsedto.AirRescuerChecklistResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/{uuid}/items/reorder [patch]
func (h *AirRescuerChecklistHandler) ReorderItems(c *fiber.Ctx) error {
	checklistID, err := uuidv7.ParseString(c.Params("uuid"))
	if err != nil {
		return validationError(c, "/path/uuid", "uuid is invalid UUID")
	}

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

	seen := make(map[string]struct{}, len(req.Data.Attributes.Items))
	rows := make([]airrescuechecklist.ChecklistItemReorderInput, 0, len(req.Data.Attributes.Items))
	for i := range req.Data.Attributes.Items {
		itemUUID := strings.TrimSpace(req.Data.Attributes.Items[i].ItemUUID)
		itemID, err := uuidv7.ParseString(itemUUID)
		if err != nil {
			return validationError(c, fmt.Sprintf("/data/attributes/items/%d/item_uuid", i), "item_uuid is invalid UUID")
		}

		itemKey := idString(itemID)
		if _, exists := seen[itemKey]; exists {
			return validationError(c, fmt.Sprintf("/data/attributes/items/%d/item_uuid", i), "item_uuid must be unique")
		}
		seen[itemKey] = struct{}{}

		rows = append(rows, airrescuechecklist.ChecklistItemReorderInput{
			ID:       itemID,
			Position: checklistPositionOrDefault(req.Data.Attributes.Items[i].Position),
		})
	}

	if err := h.svc.ReorderChecklistItems(c.UserContext(), checklistID, rows, actorUserID(c)); err != nil {
		status, errObj := buildServiceErrorObject("Reorder failed", err, "air rescuer checklist item not found")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

	return h.respondChecklistByID(c, fiber.StatusOK, checklistID)
}

// UpdateItem godoc
// @Summary Update one checklist item
// @Description Update one existing checklist item by `item_uuid`.
// @Description This endpoint only affects the selected item.
// @Description Duplicate `position` is allowed.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param item_uuid path string true "Checklist item UUID"
// @Param request body requestdto.AirRescuerChecklistItemUpdateRequest true "Checklist item update payload"
// @Success 200 {object} responsedto.AirRescuerChecklistItemResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/items/update/{item_uuid} [patch]
func (h *AirRescuerChecklistHandler) UpdateItem(c *fiber.Ctx) error {
	itemID, err := uuidv7.ParseString(c.Params("item_uuid"))
	if err != nil {
		return validationError(c, "/path/item_uuid", "item_uuid is invalid UUID")
	}

	var req requestdto.AirRescuerChecklistItemUpdateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeAirRescuerChecklistErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: safeInternalDetail(),
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, errs)
	}
	if strings.TrimSpace(req.Data.ID) != "" {
		reqID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.ID))
		if err != nil {
			return validationError(c, "/data/id", "id is invalid UUID")
		}
		if !bytesEqualUUID(reqID, itemID) {
			return validationError(c, "/data/id", "id does not match path item_uuid")
		}
	}

	attrs := req.Data.Attributes
	if attrs.Name == nil && attrs.Position == nil {
		return validationError(c, "/data/attributes", "at least one attribute must be provided")
	}

	existing, err := h.svc.GetChecklistItemByID(c.UserContext(), itemID)
	if err != nil {
		status, errObj := buildServiceErrorObject("Update failed", err, "")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}
	if existing == nil {
		return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "air rescuer checklist item not found",
		}})
	}

	name := strings.TrimSpace(existing.Name)
	if attrs.Name != nil {
		name = strings.TrimSpace(*attrs.Name)
		if name == "" {
			return validationError(c, "/data/attributes/name", "name is required")
		}
	}

	position := existing.Position
	if attrs.Position != nil {
		position = checklistPositionOrDefault(attrs.Position)
	}

	row := &airrescuechecklist.AirRescuerChecklistItem{
		ID:        itemID,
		Name:      name,
		Position:  position,
		UpdatedBy: actorUserID(c),
	}
	if err := h.svc.UpdateChecklistItem(c.UserContext(), row); err != nil {
		status, errObj := buildServiceErrorObject("Update failed", err, "air rescuer checklist item not found")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

	updated, err := h.svc.GetChecklistItemByID(c.UserContext(), itemID)
	if err != nil {
		status, errObj := buildServiceErrorObject("Update failed", err, "")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}
	if updated == nil {
		return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "air rescuer checklist item not found",
		}})
	}
	return response.Write(c, fiber.StatusOK, airRescuerChecklistItemResponseData(*updated))
}

// DeleteItem godoc
// @Summary Delete one checklist item
// @Description Delete one existing checklist item by `item_uuid`.
// @Tags Air Rescuer Checklist
// @Produce json
// @Param item_uuid path string true "Checklist item UUID"
// @Success 200 {object} jsonapi.Document
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/items/delete/{item_uuid} [delete]
func (h *AirRescuerChecklistHandler) DeleteItem(c *fiber.Ctx) error {
	itemID, err := uuidv7.ParseString(c.Params("item_uuid"))
	if err != nil {
		return validationError(c, "/path/item_uuid", "item_uuid is invalid UUID")
	}

	if err := h.svc.DeleteChecklistItem(c.UserContext(), itemID, actorUserID(c)); err != nil {
		status, errObj := buildServiceErrorObject("Delete failed", err, "air rescuer checklist item not found")
		return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
	}

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

func (h *AirRescuerChecklistHandler) respondChecklistByID(c *fiber.Ctx, status int, id []byte) error {
	resource, found, err := h.loadChecklistResource(c.UserContext(), id)
	if err != nil {
		statusCode, errObj := buildServiceErrorObject("Get failed", err, "")
		return writeAirRescuerChecklistErrors(c, statusCode, []jsonapi.ErrorObject{errObj})
	}
	if !found {
		return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "air rescuer checklist not found",
		}})
	}
	return response.Write(c, status, resource)
}

func (h *AirRescuerChecklistHandler) loadChecklistResource(ctx context.Context, id []byte) (responsedto.AirRescuerChecklistResource, bool, error) {
	head, err := h.svc.GetChecklistByID(ctx, id)
	if err != nil {
		return responsedto.AirRescuerChecklistResource{}, false, err
	}
	if head == nil {
		return responsedto.AirRescuerChecklistResource{}, false, nil
	}
	itemRows, err := h.svc.ListChecklistItemsByChecklistIDs(ctx, [][]byte{id})
	if err != nil {
		return responsedto.AirRescuerChecklistResource{}, false, err
	}
	return airRescuerChecklistResource(*head, itemRows), true, nil
}

func (h *AirRescuerChecklistHandler) loadChecklistGroups(
	ctx context.Context,
	headerFilter airrescuechecklist.ChecklistHeaderFilter,
	baseFilter airrescuechecklist.HEMSBaseFilter,
	requestedScope string,
) ([]responsedto.AirRescuerChecklistBaseGroupResource, error) {
	bases, err := h.svc.ListHEMSBases(ctx, baseFilter)
	if err != nil {
		return nil, err
	}
	headers, err := h.svc.ListChecklistHeaders(ctx, headerFilter)
	if err != nil {
		return nil, err
	}
	ids := make([][]byte, 0, len(headers))
	for i := range headers {
		ids = append(ids, headers[i].ID)
	}
	items, err := h.svc.ListChecklistItemsByChecklistIDs(ctx, ids)
	if err != nil {
		return nil, err
	}
	return buildAirRescuerChecklistGroups(bases, headers, items, requestedScope), nil
}

func parseAirRescuerChecklistFilters(c *fiber.Ctx) (airrescuechecklist.ChecklistHeaderFilter, airrescuechecklist.HEMSBaseFilter, string, error) {
	headerFilter := airrescuechecklist.ChecklistHeaderFilter{}
	baseFilter := airrescuechecklist.HEMSBaseFilter{}
	requestedScope := ""

	if categoryType, hasAnyCategory, ok := normalizeAirRescuerChecklistCategoryFromQuery(c); hasAnyCategory {
		if !ok {
			return headerFilter, baseFilter, requestedScope, validationError(c, "/query/category_type", "category_type/base_category must be one of: regular, hems")
		}
		headerFilter.CategoryType = categoryType
		baseFilter.CategoryType = categoryType
	}

	baseRaw := strings.TrimSpace(c.Query("base_id"))
	if baseRaw != "" {
		baseID, err := uuidv7.ParseString(baseRaw)
		if err != nil {
			return headerFilter, baseFilter, requestedScope, validationError(c, "/query/base_id", "base_id is invalid UUID")
		}
		headerFilter.HEMSBaseID = baseID
		baseFilter.HEMSBaseID = baseID
	}

	headerFilter.BaseName = strings.TrimSpace(c.Query("base_name"))
	headerFilter.BaseAbbreviation = strings.TrimSpace(c.Query("base_abbreviation"))
	baseFilter.BaseName = headerFilter.BaseName
	baseFilter.BaseAbbreviation = headerFilter.BaseAbbreviation

	scopeCode := airrescuechecklist.CanonicalizeAirRescuerChecklistScope(c.Query("scope_code"))
	if scopeCode != "" {
		if !airrescuechecklist.IsAirRescuerChecklistQueryScope(scopeCode) {
			return headerFilter, baseFilter, requestedScope, validationError(c, "/query/scope_code", "scope_code is invalid")
		}
		requestedScope = scopeCode
		if airrescuechecklist.IsAirRescuerChecklistPersistedScope(scopeCode) {
			headerFilter.ScopeCode = scopeCode
		}
	}

	return headerFilter, baseFilter, requestedScope, nil
}

func normalizeAirRescuerChecklistCategoryFromQuery(c *fiber.Ctx) (categoryType string, hasAny bool, ok bool) {
	// Prefer new alias first, then backward-compatible query key.
	candidates := []string{
		strings.TrimSpace(c.Query("base_category")),
		strings.TrimSpace(c.Query("category_type")),
	}
	for _, raw := range candidates {
		if raw == "" {
			continue
		}
		hasAny = true
		if normalized, valid := basedomain.NormalizeCategoryType(raw); valid {
			return normalized, true, true
		}
	}
	return "", hasAny, false
}

func checklistPositionOrDefault(v *int) int {
	if v == nil || *v <= 0 {
		return 1
	}
	return *v
}

func checklistPositionOrZero(v *int) int {
	if v == nil || *v <= 0 {
		return 0
	}
	return *v
}

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

func inferAirRescuerChecklistErrorCode(status int, title, detail string) (string, string) {
	lowerTitle := strings.ToLower(strings.TrimSpace(title))
	lowerDetail := strings.ToLower(strings.TrimSpace(detail))
	switch status {
	case fiber.StatusNotFound:
		switch {
		case strings.Contains(lowerDetail, "item not found"):
			return apperrorsx.CodeAirRescuerChecklistItemNotFound, apperrorsx.ErrAirRescuerChecklistItemNotFound.ErrorCode
		case strings.Contains(lowerDetail, "base not found"):
			return apperrorsx.CodeAirRescuerChecklistBaseNotFound, apperrorsx.ErrAirRescuerChecklistBaseNotFound.ErrorCode
		default:
			return apperrorsx.CodeAirRescuerChecklistNotFound, apperrorsx.ErrAirRescuerChecklistNotFound.ErrorCode
		}
	case fiber.StatusUnprocessableEntity:
		switch {
		case strings.Contains(lowerDetail, "scope_code is required"):
			return apperrorsx.CodeAirRescuerChecklistScopeRequired, apperrorsx.ErrAirRescuerChecklistScopeRequired.ErrorCode
		case strings.Contains(lowerDetail, "scope_code is invalid"):
			return apperrorsx.CodeAirRescuerChecklistScopeInvalid, apperrorsx.ErrAirRescuerChecklistScopeInvalid.ErrorCode
		case strings.Contains(lowerDetail, "title is required"):
			return apperrorsx.CodeAirRescuerChecklistTitleRequired, apperrorsx.ErrAirRescuerChecklistTitleRequired.ErrorCode
		case strings.Contains(lowerDetail, "name is required"):
			return apperrorsx.CodeAirRescuerChecklistNameRequired, apperrorsx.ErrAirRescuerChecklistNameRequired.ErrorCode
		case strings.Contains(lowerDetail, "id does not match path"):
			return apperrorsx.CodeAirRescuerChecklistIDMismatch, apperrorsx.ErrAirRescuerChecklistIDMismatch.ErrorCode
		case strings.Contains(lowerDetail, "invalid uuid"):
			return apperrorsx.CodeAirRescuerChecklistInvalidUUID, apperrorsx.ErrAirRescuerChecklistInvalidUUID.ErrorCode
		default:
			return apperrorsx.CodeAirRescuerChecklistValidationFailed, apperrorsx.ErrAirRescuerChecklistValidationFailed.ErrorCode
		}
	case fiber.StatusBadRequest:
		switch {
		case strings.Contains(lowerTitle, "invalid json"):
			return apperrorsx.CodeAirRescuerChecklistInvalidJSON, apperrorsx.ErrAirRescuerChecklistInvalidJSON.ErrorCode
		case strings.Contains(lowerTitle, "create failed"):
			return apperrorsx.CodeAirRescuerChecklistCreateFailed, apperrorsx.ErrAirRescuerChecklistCreateFailed.ErrorCode
		case strings.Contains(lowerTitle, "update failed"):
			return apperrorsx.CodeAirRescuerChecklistUpdateFailed, apperrorsx.ErrAirRescuerChecklistUpdateFailed.ErrorCode
		case strings.Contains(lowerTitle, "delete failed"):
			return apperrorsx.CodeAirRescuerChecklistDeleteFailed, apperrorsx.ErrAirRescuerChecklistDeleteFailed.ErrorCode
		case strings.Contains(lowerTitle, "list failed"), strings.Contains(lowerTitle, "get failed"):
			return apperrorsx.CodeAirRescuerChecklistListFailed, apperrorsx.ErrAirRescuerChecklistListFailed.ErrorCode
		case strings.Contains(lowerTitle, "reorder failed"):
			return apperrorsx.CodeAirRescuerChecklistReorderFailed, apperrorsx.ErrAirRescuerChecklistReorderFailed.ErrorCode
		default:
			return apperrorsx.CodeAirRescuerChecklistInvalidPayload, apperrorsx.ErrAirRescuerChecklistInvalidPayload.ErrorCode
		}
	default:
		return apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode
	}
}

func validationError(c *fiber.Ctx, pointer, detail string) error {
	return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
		Status: "422",
		Title:  "Validation error",
		Detail: detail,
		Source: &jsonapi.ErrorSource{Pointer: pointer},
	}})
}
