package rest

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/features/metadata"
	"github.com/nexuscrm/backend/internal/features/records"
	"github.com/nexuscrm/backend/pkg/apierr"
	"github.com/nexuscrm/shared/pkg/models"
)

type CompositeCollectionsHandler struct {
	recordSvc   *records.PersistenceService
	metadataSvc *metadata.MetadataService
}

func NewCompositeCollectionsHandler(recordSvc *records.PersistenceService, metadataSvc *metadata.MetadataService) *CompositeCollectionsHandler {
	return &CompositeCollectionsHandler{
		recordSvc:   recordSvc,
		metadataSvc: metadataSvc,
	}
}

// HandleCollectionsCreate handles POST /composite/sobjects
func (h *CompositeCollectionsHandler) HandleCollectionsCreate(c *gin.Context) {
	var req struct {
		AllOrNone bool             `json:"allOrNone"`
		Records   []models.SObject `json:"records"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Invalid request body"))
		return
	}

	if len(req.Records) > 200 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeLimitExceeded, "Maximum 200 records allowed"))
		return
	}

	user := MustGetUser(c)
	results, err := h.recordSvc.CreateRecords(c.Request.Context(), req.Records, req.AllOrNone, user)
	if err != nil {
		// allOrNone=true batch failure: preserve SF's INVALID_FIELD_FOR_INSERT_UPDATE
		// shape when an FLS denial caused the rollback.
		WriteError(c, err, apierr.SFCodeInvalidRequest)
		return
	}

	c.JSON(http.StatusOK, results)
}

// HandleCollectionsUpdate handles PATCH /composite/sobjects
func (h *CompositeCollectionsHandler) HandleCollectionsUpdate(c *gin.Context) {
	var req struct {
		AllOrNone bool             `json:"allOrNone"`
		Records   []models.SObject `json:"records"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Invalid request body"))
		return
	}

	if len(req.Records) > 200 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeLimitExceeded, "Maximum 200 records allowed"))
		return
	}

	user := MustGetUser(c)
	results, err := h.recordSvc.UpdateRecords(c.Request.Context(), req.Records, req.AllOrNone, user)
	if err != nil {
		// allOrNone=true batch failure: preserve SF's INVALID_FIELD_FOR_INSERT_UPDATE
		// shape when an FLS denial caused the rollback.
		WriteError(c, err, apierr.SFCodeInvalidRequest)
		return
	}
	c.JSON(http.StatusOK, results)
}

// HandleCollectionsDelete handles DELETE /composite/sobjects?ids=...&allOrNone=...
func (h *CompositeCollectionsHandler) HandleCollectionsDelete(c *gin.Context) {
	idsParam := c.Query("ids")
	allOrNone := c.Query("allOrNone") == "true"

	if idsParam == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Missing ids parameter"))
		return
	}

	ids := strings.Split(idsParam, ",")
	if len(ids) > 200 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeLimitExceeded, "Maximum 200 IDs allowed"))
		return
	}

	// Handle Delete logic
	user := MustGetUser(c)
	results, err := h.recordSvc.DeleteRecords(c.Request.Context(), ids, allOrNone, user)
	if err != nil {
		WriteError(c, err, apierr.SFCodeInvalidRequest)
		return
	}

	c.JSON(http.StatusOK, results)
}

// HandleCollectionsRetrieve handles GET /composite/sobjects?ids=...&fields=...
func (h *CompositeCollectionsHandler) HandleCollectionsRetrieve(c *gin.Context) {
	idsParam := c.Query("ids")
	fieldsParam := c.Query("fields")
	if idsParam == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Missing ids parameter"))
		return
	}

	ids := strings.Split(idsParam, ",")
	if len(ids) > 200 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeLimitExceeded, "Maximum 200 IDs allowed"))
		return
	}

	var fields []string
	if fieldsParam != "" {
		fields = strings.Split(fieldsParam, ",")
	}

	user := MustGetUser(c)
	// We pass empty objectType to enable Global Type Discovery (Salesforce Parity)
	results, err := h.recordSvc.RetrieveRecords(c.Request.Context(), "", ids, fields, user)
	if err != nil {
		WriteError(c, err, apierr.SFCodeInvalidRequest)
		return
	}

	c.JSON(http.StatusOK, results)
}

// HandleCollectionsUpsert handles PATCH /composite/sobjects/:sObjectName/:externalIdFieldName
// Salesforce SObject Collections Upsert — upsert records by external ID field.
func (h *CompositeCollectionsHandler) HandleCollectionsUpsert(c *gin.Context) {
	sObjectName := c.Param("sObjectName")
	externalIdField := c.Param("externalIdFieldName")

	if sObjectName == "" || externalIdField == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "sObjectName and externalIdFieldName are required"))
		return
	}

	var req struct {
		AllOrNone bool             `json:"allOrNone"`
		Records   []models.SObject `json:"records"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Invalid request body"))
		return
	}

	if len(req.Records) > 200 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeLimitExceeded, "Maximum 200 records allowed"))
		return
	}

	user := MustGetUser(c)

	// Upsert: for each record, try to find by external ID field, update if found, create if not
	results, err := h.recordSvc.UpsertRecords(c.Request.Context(), sObjectName, externalIdField, req.Records, req.AllOrNone, user)
	if err != nil {
		// Unique to Upsert: a direct *apierr.NotFound (e.g. target
		// sObject schema not found — persistence_bulk_ops.go:~824)
		// must surface as 404 NOT_FOUND, not be flattened to 400.
		// Plain type assertion (not errors.As) is load-bearing: any
		// InternalError(cause=*apierr.Error) envelope must still hit
		// WriteError → IsInternal → 500, not be unwrapped here.
		// Translate apiErr.Status → SF code explicitly — apiErr.Code
		// is internal PascalCase ("ResourceNotFound") not an SF code.
		if apiErr, ok := err.(*apierr.Error); ok && apiErr.Status == http.StatusNotFound {
			c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, apiErr.Message))
			return
		}
		WriteError(c, err, apierr.SFCodeInvalidRequest)
		return
	}

	c.JSON(http.StatusOK, results)
}
