package rest

import (
	"errors"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/pkg/apierr"
	appErrors "github.com/nexuscrm/backend/pkg/errors"
)

// RespondError classifies a service-layer error and writes a
// Salesforce-style error response. Returns true when the error was
// recognised (caller should return immediately); returns false when
// the caller must write its own operation-specific fallback (typically
// a 400 with an op-specific SF code like SFCodeInsertFailed).
//
// Ordering is load-bearing: IsInternal is checked FIRST. An
// InternalError may wrap an *apierr.Error — for example the tx-manager
// envelope wraps a 4xx cause on rollback — and errors.As walks the
// Unwrap chain, so any later errors.As(*apierr.Error) assertion would
// unwrap through the envelope and misclassify a genuine infra failure
// as a 4xx response. Every record-mutation handler must go through
// this helper; re-inlining the checks is how handlers drift out of
// compliance (see PR #1184 gate review).
func RespondError(c *gin.Context, err error) bool {
	if err == nil {
		return false
	}

	if appErrors.IsInternal(err) {
		c.JSON(http.StatusInternalServerError,
			apierr.SFErrorResponse(apierr.SFCodeInternalError, err.Error()))
		return true
	}

	var permErr *appErrors.PermissionError
	if errors.As(err, &permErr) {
		c.JSON(http.StatusForbidden,
			apierr.SFErrorResponse(apierr.SFCodeForbidden, err.Error()))
		return true
	}

	var apiErr *apierr.Error
	if errors.As(err, &apiErr) && apiErr.Status == http.StatusForbidden {
		c.JSON(http.StatusForbidden,
			apierr.SFErrorResponse(apierr.SFCodeForbidden, err.Error()))
		return true
	}

	return false
}

// WriteError is the canonical write-path error responder for every
// record-mutation handler (sobject_records Create/Update/Delete,
// sobject_views UpsertByExternalID, and all five composite collection
// handlers). It chains
// the generic classification from RespondError with the FLS-denied
// response shape that SF emits for write-path FLS / immutable-field
// rejections, falling back to a 400 with the caller's operation-specific
// code (SFCodeInsertFailed, SFCodeUpdateFailed, SFCodeUpsertFailed,
// SFCodeInvalidRequest, etc.).
//
// The ordering is load-bearing — see RespondError's comment for why
// IsInternal must precede any errors.As unwrap. FLS must come AFTER
// RespondError so an InternalError(cause=FLSDeniedError) — possible on
// tx-manager rollback — is still reported as 500, not as a 400 FLS
// denial that hides the real infra failure.
//
// Handlers with exotic needs (e.g. composite Upsert, which passes
// through non-403 *apierr.Error status codes) should do those extra
// branches inline BEFORE calling WriteError — not by extending this
// helper. Keep the helper narrow so the contract stays auditable.
func WriteError(c *gin.Context, err error, fallbackCode string) {
	if err == nil {
		return
	}
	if RespondError(c, err) {
		return
	}
	var sfErr *apierr.SalesforceAPIError
	if errors.As(err, &sfErr) {
		c.JSON(sfErr.Status, []apierr.SFError{sfErr.Err})
		return
	}
	if flsErr, ok := appErrors.AsFLSDenied(err); ok {
		c.JSON(http.StatusBadRequest, apierr.SFErrorWithFields(
			apierr.SFCodeInvalidFieldForInsertUpdate, flsErr.Message, flsErr.Fields...,
		))
		return
	}
	c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(fallbackCode, err.Error()))
}
