package handlers

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

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

	filemanager "wucher/internal/domain/file_manager"
	mastersettings "wucher/internal/domain/master_settings"
	"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 MasterSettingsHandler struct {
	svc      mastersettings.Service
	fileSvc  filemanager.Service
	storage  filemanager.ObjectStorage
	validate *validators.Validator
}

type managedFileAsset struct {
	OriginalURL       *string
	ThumbnailURL      *string
	OriginalSizeBytes *int64
}

func NewMasterSettingsHandler(svc mastersettings.Service, fileSvc filemanager.Service, storage filemanager.ObjectStorage) *MasterSettingsHandler {
	return &MasterSettingsHandler{
		svc:      svc,
		fileSvc:  fileSvc,
		storage:  storage,
		validate: validators.New(),
	}
}

// CreateMasterSettings godoc
// @Summary Create master settings
// @Description Create a master settings row.
// @Tags Master Settings
// @Accept json
// @Produce json
// @Param request body requestdto.MasterSettingsCreateRequest true "JSON:API master settings create request"
// @Success 201 {object} responsedto.MasterSettingsResponse
// @Failure 422 {object} jsonapi.ErrorDocument
// @Failure 400 {object} jsonapi.ErrorDocument
// @Router /api/v1/master-settings/create [post]
func (h *MasterSettingsHandler) Create(c *fiber.Ctx) error {
	var req requestdto.MasterSettingsCreateRequest
	if err := c.BodyParser(&req); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: safeInternalDetail(),
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeMasterSettingsErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	companyName := strings.TrimSpace(req.Data.Attributes.CompanyName)
	if companyName == "" {
		return writeMasterSettingsErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "company_name is required",
			Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/company_name"},
		}})
	}

	logoFileID, statusCode, errObj := h.resolveLogoFileID(c.UserContext(), req.Data.Attributes.LogoFileUUID, "/data/attributes/logo_file_uuid")
	if errObj != nil {
		return writeMasterSettingsErrors(c, statusCode, []jsonapi.ErrorObject{*errObj})
	}
	coverFileID, statusCode, errObj := h.resolveCoverFileID(c.UserContext(), req.Data.Attributes.CoverFileUUID, "/data/attributes/cover_file_uuid")
	if errObj != nil {
		return writeMasterSettingsErrors(c, statusCode, []jsonapi.ErrorObject{*errObj})
	}

	row := &mastersettings.MasterSettings{
		CompanyName: companyName,
		Title:       optionalTrimmedString(req.Data.Attributes.Title),
		Subtitle:    optionalTrimmedString(req.Data.Attributes.Subtitle),
		LogoFileID:  logoFileID,
		CoverFileID: coverFileID,
		LogoURL:     nil,
		CreatedBy:   actorUserID(c),
		UpdatedBy:   actorUserID(c),
	}

	if err := h.svc.Create(c.UserContext(), row); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncFileLifecycleSwap(c.UserContext(), h.fileSvc, nil, row.LogoFileID, actorUserID(c)); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Create failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncFileLifecycleSwap(c.UserContext(), h.fileSvc, nil, row.CoverFileID, actorUserID(c)); err != nil {
		return writeMasterSettingsErrors(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, h.masterSettingsResource(c, created))
	}
	return response.Write(c, fiber.StatusCreated, h.masterSettingsResource(c, row))
}

// GetMasterSettings godoc
// @Summary Get current master settings
// @Description Return singleton master settings object (latest active row).
// @Tags Master Settings
// @Produce json
// @Success 200 {object} responsedto.MasterSettingsResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Router /api/v1/master-settings/get [get]
func (h *MasterSettingsHandler) Get(c *fiber.Ctx) error {
	rows, _, err := h.svc.List(c.UserContext(), "", "-updated_at", 1, 0)
	if err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Get failed",
			Detail: safeInternalDetail(),
		}})
	}

	if len(rows) == 0 {
		return response.Write(c, fiber.StatusOK, responsedto.MasterSettingsResponse{
			Data: responsedto.MasterSettingsResource{
				Type: "master_settings",
				ID:   "",
				Attributes: responsedto.MasterSettingsAttributes{
					CompanyName: "",
				},
			},
		})
	}

	return response.Write(c, fiber.StatusOK, h.masterSettingsResource(c, &rows[0]))
}

// UpsertMicrosoftEntraSSO godoc
// @Summary Upsert Microsoft Entra SSO settings
// @Description Create or update Microsoft Entra SSO settings as master settings.
// @Tags Master Settings
// @Accept json
// @Produce json
// @Param request body requestdto.MasterSettingsMicrosoftEntraUpsertRequest true "JSON:API Microsoft Entra SSO upsert request"
// @Success 200 {object} responsedto.MasterSettingsMicrosoftEntraResponse
// @Failure 422 {object} jsonapi.ErrorDocument
// @Failure 400 {object} jsonapi.ErrorDocument
// @Router /api/v1/master-settings/sso/microsoft [put]
func (h *MasterSettingsHandler) UpsertMicrosoftEntraSSO(c *fiber.Ctx) error {
	var req requestdto.MasterSettingsMicrosoftEntraUpsertRequest
	if err := c.BodyParser(&req); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Invalid JSON",
			Detail: safeInternalDetail(),
		}})
	}
	if errs := h.validate.ValidateStruct(req); errs != nil {
		return writeMasterSettingsErrors(c, fiber.StatusUnprocessableEntity, errs)
	}

	view, err := h.svc.UpsertMicrosoftEntraConfig(c.UserContext(), mastersettings.MicrosoftEntraConfigInput{
		TenantID:     req.Data.Attributes.TenantID,
		ClientID:     req.Data.Attributes.ClientID,
		ClientSecret: req.Data.Attributes.ClientSecret,
		RedirectURL:  req.Data.Attributes.RedirectURL,
		Authority:    req.Data.Attributes.Authority,
		Scopes:       req.Data.Attributes.Scopes,
	}, actorUserID(c))
	if err != nil {
		status := fiber.StatusBadRequest
		title := "Upsert failed"
		if isMicrosoftEntraValidationError(err) {
			status = fiber.StatusUnprocessableEntity
			title = "Validation error"
		}
		return writeMasterSettingsErrors(c, status, []jsonapi.ErrorObject{{
			Status: strconv.Itoa(status),
			Title:  title,
			Detail: safeInternalDetail(),
		}})
	}

	return response.Write(c, fiber.StatusOK, h.microsoftEntraResource(view))
}

// GetMicrosoftEntraSSO godoc
// @Summary Get Microsoft Entra SSO settings
// @Description Get Microsoft Entra SSO settings from master settings.
// @Tags Master Settings
// @Produce json
// @Success 200 {object} responsedto.MasterSettingsMicrosoftEntraResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 400 {object} jsonapi.ErrorDocument
// @Router /api/v1/master-settings/sso/microsoft [get]
func (h *MasterSettingsHandler) GetMicrosoftEntraSSO(c *fiber.Ctx) error {
	view, err := h.svc.GetMicrosoftEntraConfig(c.UserContext())
	if err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Get failed",
			Detail: safeInternalDetail(),
		}})
	}
	if view == nil {
		return writeMasterSettingsErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "microsoft entra sso settings not found",
		}})
	}

	return response.Write(c, fiber.StatusOK, h.microsoftEntraResource(view))
}

// UpdateMasterSettings godoc
// @Summary Update master settings (partial)
// @Description Patch master settings by ID.
// @Tags Master Settings
// @Accept json
// @Produce json
// @Param uuid path string true "Master Settings UUID (UUIDv7)"
// @Param request body requestdto.MasterSettingsUpdateRequest true "JSON:API master settings update request"
// @Success 200 {object} responsedto.MasterSettingsResponse
// @Failure 422 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Router /api/v1/master-settings/update/{uuid} [patch]
func (h *MasterSettingsHandler) Update(c *fiber.Ctx) error {
	uuidStr := c.Params("uuid")
	id, err := uuidv7.ParseString(uuidStr)
	if err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "uuid is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: "/path/uuid"},
		}})
	}

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

	if strings.TrimSpace(req.Data.ID) == "" {
		return writeMasterSettingsErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "id is required",
			Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
		}})
	}
	if req.Data.ID != uuidStr {
		return writeMasterSettingsErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
			Status: "422",
			Title:  "Validation error",
			Detail: "id does not match path uuid",
			Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
		}})
	}

	attrs := req.Data.Attributes
	if attrs.CompanyName == nil &&
		attrs.Title == nil &&
		attrs.Subtitle == nil &&
		attrs.LogoFileUUID == nil &&
		attrs.CoverFileUUID == nil {
		return writeMasterSettingsErrors(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 writeMasterSettingsErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
			Status: "404",
			Title:  "Not found",
			Detail: "master settings not found",
		}})
	}
	prevLogoFileID := append([]byte(nil), existing.LogoFileID...)
	prevCoverFileID := append([]byte(nil), existing.CoverFileID...)

	if attrs.CompanyName != nil {
		v := strings.TrimSpace(*attrs.CompanyName)
		if v == "" {
			return writeMasterSettingsErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
				Status: "422",
				Title:  "Validation error",
				Detail: "company_name cannot be empty",
				Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/company_name"},
			}})
		}
		existing.CompanyName = v
	}
	if attrs.Title != nil {
		existing.Title = strings.TrimSpace(*attrs.Title)
	}
	if attrs.Subtitle != nil {
		existing.Subtitle = strings.TrimSpace(*attrs.Subtitle)
	}
	if attrs.LogoFileUUID != nil {
		if hasBlankOptionalString(attrs.LogoFileUUID) {
			existing.LogoFileID = nil
		} else {
			logoFileID, statusCode, errObj := h.resolveLogoFileID(c.UserContext(), attrs.LogoFileUUID, "/data/attributes/logo_file_uuid")
			if errObj != nil {
				return writeMasterSettingsErrors(c, statusCode, []jsonapi.ErrorObject{*errObj})
			}
			existing.LogoFileID = logoFileID
			// Keep manual URL disabled once logo is managed by file manager metadata.
			existing.LogoURL = nil
		}
	}
	if attrs.CoverFileUUID != nil {
		if hasBlankOptionalString(attrs.CoverFileUUID) {
			existing.CoverFileID = nil
		} else {
			coverFileID, statusCode, errObj := h.resolveCoverFileID(c.UserContext(), attrs.CoverFileUUID, "/data/attributes/cover_file_uuid")
			if errObj != nil {
				return writeMasterSettingsErrors(c, statusCode, []jsonapi.ErrorObject{*errObj})
			}
			existing.CoverFileID = coverFileID
		}
	}
	existing.UpdatedBy = actorUserID(c)

	if err := h.svc.Update(c.UserContext(), existing); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncFileLifecycleSwap(c.UserContext(), h.fileSvc, prevLogoFileID, existing.LogoFileID, actorUserID(c)); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Update failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncFileLifecycleSwap(c.UserContext(), h.fileSvc, prevCoverFileID, existing.CoverFileID, actorUserID(c)); err != nil {
		return writeMasterSettingsErrors(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, h.masterSettingsResource(c, updated))
	}
	return response.Write(c, fiber.StatusOK, h.masterSettingsResource(c, existing))
}

// DeleteMasterSettings godoc
// @Summary Delete master settings
// @Tags Master Settings
// @Produce json
// @Param uuid path string true "Master Settings UUID (UUIDv7)"
// @Success 200 {object} jsonapi.Resource
// @Failure 422 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Router /api/v1/master-settings/delete/{uuid} [delete]
func (h *MasterSettingsHandler) Delete(c *fiber.Ctx) error {
	uuidStr := c.Params("uuid")
	id, err := uuidv7.ParseString(uuidStr)
	if err != nil {
		return writeMasterSettingsErrors(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 prevLogoFileID []byte
	var prevCoverFileID []byte
	if existing != nil {
		prevLogoFileID = append([]byte(nil), existing.LogoFileID...)
		prevCoverFileID = append([]byte(nil), existing.CoverFileID...)
	}

	if err := h.svc.Delete(c.UserContext(), id, actorUserID(c)); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return writeMasterSettingsErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
				Status: "404",
				Title:  "Not found",
				Detail: "master settings not found",
			}})
		}
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncFileLifecycleSwap(c.UserContext(), h.fileSvc, prevLogoFileID, nil, actorUserID(c)); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: safeInternalDetail(),
		}})
	}
	if err := syncFileLifecycleSwap(c.UserContext(), h.fileSvc, prevCoverFileID, nil, actorUserID(c)); err != nil {
		return writeMasterSettingsErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "Delete failed",
			Detail: safeInternalDetail(),
		}})
	}

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

func (h *MasterSettingsHandler) masterSettingsResource(c *fiber.Ctx, row *mastersettings.MasterSettings) responsedto.MasterSettingsResource {
	logoFileUUID := uuidStringPtrOrNil(row.LogoFileID)
	logoURL := h.resolveLogoURL(c, row, logoFileUUID)
	logoThumbURL := h.resolveLogoThumbnailURL(c, row, logoFileUUID)
	logoSize := h.resolveLogoOriginalSize(c, row, logoFileUUID)
	coverFileUUID := uuidStringPtrOrNil(row.CoverFileID)
	coverURL := h.resolveCoverURL(c, row, coverFileUUID)
	coverThumbURL := h.resolveCoverThumbnailURL(c, row, coverFileUUID)
	coverSize := h.resolveCoverOriginalSize(c, row, coverFileUUID)

	return responsedto.MasterSettingsResource{
		Type: "master_settings",
		ID:   idString(row.ID),
		Attributes: responsedto.MasterSettingsAttributes{
			CompanyName:    row.CompanyName,
			Title:          row.Title,
			Subtitle:       row.Subtitle,
			LogoFileUUID:   logoFileUUID,
			LogoURL:        logoURL,
			LogoThumbURL:   logoThumbURL,
			LogoSizeBytes:  logoSize,
			CoverFileUUID:  coverFileUUID,
			CoverURL:       coverURL,
			CoverThumbURL:  coverThumbURL,
			CoverSizeBytes: coverSize,
			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 optionalTrimmedString(v *string) string {
	if v == nil {
		return ""
	}
	return strings.TrimSpace(*v)
}

func (h *MasterSettingsHandler) resolveLogoURL(c *fiber.Ctx, row *mastersettings.MasterSettings, logoFileUUID *string) *string {
	legacyURL := strings.TrimSpace(masterSettingsDerefString(row.LogoURL))
	if logoFileUUID == nil || strings.TrimSpace(*logoFileUUID) == "" {
		if legacyURL == "" {
			return nil
		}
		return stringPointer(legacyURL)
	}

	return h.resolveManagedFileAsset(c, row.LogoFileID, logoFileUUID).OriginalURL
}

func (h *MasterSettingsHandler) resolveCoverURL(c *fiber.Ctx, row *mastersettings.MasterSettings, coverFileUUID *string) *string {
	return h.resolveManagedFileAsset(c, row.CoverFileID, coverFileUUID).OriginalURL
}

func (h *MasterSettingsHandler) resolveManagedFileURL(c *fiber.Ctx, fileID []byte, fileUUID *string) *string {
	return h.resolveManagedFileAsset(c, fileID, fileUUID).OriginalURL
}

func (h *MasterSettingsHandler) resolveLogoThumbnailURL(c *fiber.Ctx, row *mastersettings.MasterSettings, logoFileUUID *string) *string {
	if row == nil {
		return nil
	}
	return h.resolveManagedFileAsset(c, row.LogoFileID, logoFileUUID).ThumbnailURL
}

func (h *MasterSettingsHandler) resolveCoverThumbnailURL(c *fiber.Ctx, row *mastersettings.MasterSettings, coverFileUUID *string) *string {
	if row == nil {
		return nil
	}
	return h.resolveManagedFileAsset(c, row.CoverFileID, coverFileUUID).ThumbnailURL
}

func (h *MasterSettingsHandler) resolveLogoOriginalSize(c *fiber.Ctx, row *mastersettings.MasterSettings, logoFileUUID *string) *int64 {
	if row == nil {
		return nil
	}
	return h.resolveManagedFileAsset(c, row.LogoFileID, logoFileUUID).OriginalSizeBytes
}

func (h *MasterSettingsHandler) resolveCoverOriginalSize(c *fiber.Ctx, row *mastersettings.MasterSettings, coverFileUUID *string) *int64 {
	if row == nil {
		return nil
	}
	return h.resolveManagedFileAsset(c, row.CoverFileID, coverFileUUID).OriginalSizeBytes
}

func (h *MasterSettingsHandler) resolveManagedFileAsset(c *fiber.Ctx, fileID []byte, fileUUID *string) managedFileAsset {
	if fileUUID == nil || strings.TrimSpace(*fileUUID) == "" {
		return managedFileAsset{}
	}

	if h.fileSvc == nil {
		return managedFileAsset{}
	}

	fileRow, err := h.fileSvc.GetFileByID(c.UserContext(), fileID)
	if err != nil || fileRow == nil {
		return managedFileAsset{}
	}

	resolved := resolveFileManagerDownloadURLs(c.UserContext(), h.storage, fileRow)
	asset := managedFileAsset{
		OriginalSizeBytes: int64Ptr(resolved.OriginalSizeBytes),
	}
	if url := strings.TrimSpace(resolved.OriginalURL); url != "" {
		asset.OriginalURL = stringPointer(url)
	}
	if url := strings.TrimSpace(resolved.ThumbnailURL); url != "" {
		asset.ThumbnailURL = stringPointer(url)
	}
	return asset
}

func stringPointer(v string) *string {
	return &v
}

func masterSettingsDerefString(v *string) string {
	if v == nil {
		return ""
	}
	return *v
}

func uuidStringPtrOrNil(id []byte) *string {
	v := idString(id)
	if strings.TrimSpace(v) == "" {
		return nil
	}
	return &v
}

func (h *MasterSettingsHandler) microsoftEntraResource(view *mastersettings.MicrosoftEntraConfigView) responsedto.MasterSettingsMicrosoftEntraResource {
	if view == nil {
		return responsedto.MasterSettingsMicrosoftEntraResource{Type: "master_settings_microsoft_entra_sso"}
	}
	return responsedto.MasterSettingsMicrosoftEntraResource{
		Type: "master_settings_microsoft_entra_sso",
		Attributes: responsedto.MasterSettingsMicrosoftEntraAttributes{
			TenantID:           view.TenantID,
			ClientID:           view.ClientID,
			ClientSecretMasked: view.ClientSecretMasked,
			RedirectURL:        view.RedirectURL,
			Authority:          view.Authority,
			Scopes:             view.Scopes,
		},
	}
}

func isMicrosoftEntraValidationError(err error) bool {
	if err == nil {
		return false
	}
	msg := err.Error()
	return strings.Contains(msg, "MS_ENTRA_") ||
		strings.Contains(msg, "required") ||
		strings.Contains(msg, "valid absolute URL")
}

func (h *MasterSettingsHandler) resolveLogoFileID(ctx context.Context, raw *string, pointer string) ([]byte, int, *jsonapi.ErrorObject) {
	return h.resolveManagedFileID(ctx, raw, pointer, "logo_file_uuid")
}

func (h *MasterSettingsHandler) resolveCoverFileID(ctx context.Context, raw *string, pointer string) ([]byte, int, *jsonapi.ErrorObject) {
	return h.resolveManagedFileID(ctx, raw, pointer, "cover_file_uuid")
}

func (h *MasterSettingsHandler) resolveManagedFileID(ctx context.Context, raw *string, pointer, fieldName string) ([]byte, int, *jsonapi.ErrorObject) {
	if raw == nil {
		return nil, 0, nil
	}

	v := strings.TrimSpace(*raw)
	if v == "" {
		return nil, 0, nil
	}

	id, err := uuidv7.ParseString(v)
	if err != nil {
		return nil, fiber.StatusUnprocessableEntity, &jsonapi.ErrorObject{
			Status: "422",
			Title:  "Validation error",
			Detail: fieldName + " is invalid UUID",
			Source: &jsonapi.ErrorSource{Pointer: pointer},
		}
	}

	if h.fileSvc == nil {
		return nil, fiber.StatusServiceUnavailable, &jsonapi.ErrorObject{
			Status: "503",
			Title:  "File manager unavailable",
			Detail: "file manager service is not configured",
			Source: &jsonapi.ErrorSource{Pointer: pointer},
		}
	}

	fileRow, err := h.fileSvc.GetFileByID(ctx, id)
	if err != nil {
		return nil, fiber.StatusBadRequest, &jsonapi.ErrorObject{
			Status: "400",
			Title:  "Validation error",
			Detail: safeInternalDetail(),
			Source: &jsonapi.ErrorSource{Pointer: pointer},
		}
	}
	if fileRow == nil {
		return nil, fiber.StatusUnprocessableEntity, &jsonapi.ErrorObject{
			Status: "422",
			Title:  "Validation error",
			Detail: fieldName + " is not found",
			Source: &jsonapi.ErrorSource{Pointer: pointer},
		}
	}
	if !strings.EqualFold(strings.TrimSpace(fileRow.Status), filemanager.FileStatusReady) {
		return nil, fiber.StatusUnprocessableEntity, &jsonapi.ErrorObject{
			Status: "422",
			Title:  "Validation error",
			Detail: fieldName + " is not ready. finalize upload via /api/v1/file-manager/files/create",
			Source: &jsonapi.ErrorSource{Pointer: pointer},
		}
	}

	return id, 0, nil
}

func writeMasterSettingsErrors(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
		def := inferMasterSettingsDef(status, e.Title, e.Detail)
		if strings.TrimSpace(e.Code) == "" {
			e.Code = def.Code
		}
		if strings.TrimSpace(e.ErrorCode) == "" {
			e.ErrorCode = def.ErrorCode
		}
		if strings.TrimSpace(e.Title) == "" {
			e.Title = def.Title
		}
		e.Detail = def.Message
		enriched = append(enriched, e)
	}
	return response.WriteErrors(c, status, enriched)
}

func inferMasterSettingsDef(status int, title, detail string) apperrorsx.Def {
	lowerTitle := strings.ToLower(strings.TrimSpace(title))
	lowerDetail := strings.ToLower(strings.TrimSpace(detail))
	switch status {
	case fiber.StatusNotFound:
		return apperrorsx.ErrMasterSettingsNotFound
	case fiber.StatusUnprocessableEntity:
		switch {
		case strings.Contains(lowerDetail, "uuid is invalid"):
			return apperrorsx.ErrMasterSettingsInvalidUUID
		case strings.Contains(lowerDetail, "id is required"):
			return apperrorsx.ErrMasterSettingsIDRequired
		case strings.Contains(lowerDetail, "id does not match"):
			return apperrorsx.ErrMasterSettingsIDMismatch
		case strings.Contains(lowerDetail, "at least one attribute"):
			return apperrorsx.ErrMasterSettingsAttributesRequired
		case strings.Contains(lowerDetail, "company_name is required"), strings.Contains(lowerDetail, "company_name cannot be empty"):
			return apperrorsx.ErrMasterSettingsCompanyNameRequired
		case strings.Contains(lowerDetail, "logo_file_uuid"):
			return apperrorsx.ErrMasterSettingsLogoFileInvalid
		case strings.Contains(lowerDetail, "cover_file_uuid"):
			return apperrorsx.ErrMasterSettingsCoverFileInvalid
		default:
			return apperrorsx.ErrMasterSettingsInvalidPayload
		}
	case fiber.StatusBadRequest:
		switch {
		case strings.Contains(lowerTitle, "invalid json"):
			return apperrorsx.ErrMasterSettingsInvalidJSON
		case strings.Contains(lowerTitle, "create failed"):
			return apperrorsx.ErrMasterSettingsCreateFailed
		case strings.Contains(lowerTitle, "update failed"):
			return apperrorsx.ErrMasterSettingsUpdateFailed
		case strings.Contains(lowerTitle, "delete failed"):
			return apperrorsx.ErrMasterSettingsDeleteFailed
		case strings.Contains(lowerTitle, "get failed"):
			return apperrorsx.ErrMasterSettingsGetFailed
		case strings.Contains(lowerTitle, "upsert failed"):
			return apperrorsx.ErrMasterSettingsMicrosoftUpsert
		default:
			return apperrorsx.ErrMasterSettingsInvalidPayload
		}
	default:
		return apperrorsx.ErrInternal
	}
}
