package rest

import (
	"context"
	"strings"

	"github.com/gin-gonic/gin"
	appErrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// --- Schema Handlers ---

// GetSchemas godoc
// @Summary      List Object Schemas
// @Description  Get metadata for all registered objects.
// @Tags         metadata
// @Accept       json
// @Produce      json
// @Success      200  {object}  map[string]interface{} "Wrapped list of schemas"
// @Router       /metadata/objects [get]
// @Security     BearerAuth
func (h *MetadataHandler) GetSchemas(c *gin.Context) {
	schemas := h.svc.GetSchemas(c.Request.Context())
	c.JSON(200, map[string]interface{}{"sobjects": schemas})
}

// GetSchema godoc
// @Summary      Get Object Schema
// @Description  Get metadata for a specific object by API Name.
// @Tags         metadata
// @Accept       json
// @Produce      json
// @Param        apiName path string true "Object API Name"
// @Success      200  {object}  models.ObjectMetadata
// @Failure      404  {object}  map[string]interface{}
// @Router       /metadata/objects/{apiName} [get]
// @Security     BearerAuth
func (h *MetadataHandler) GetSchema(c *gin.Context) {
	user := MustGetUser(c)
	apiName := strings.ToLower(c.Param("apiName"))

	// Get effective schema (filtered by permissions)
	schema := h.svc.GetEffectiveSchema(c.Request.Context(), apiName, user)
	if schema == nil {
		RespondAppError(c, appErrors.NewNotFoundError("Schema", apiName))
		return
	}
	c.JSON(200, schema)
}

// CreateSchema godoc
// @Summary      Create Object Schema
// @Description  Create a new custom object definition.
// @Tags         metadata
// @Accept       json
// @Produce      json
// @Param        schema body models.ObjectMetadata true "Object Metadata"
// @Success      201  {object}  models.ObjectMetadata
// @Failure      400  {object}  map[string]interface{}
// @Router       /metadata/objects [post]
// @Security     BearerAuth
func (h *MetadataHandler) CreateSchema(c *gin.Context) {
	// requireSystemAdmin handled by middleware

	// Define request structure to support ONLY native Salesforce camelCase
	// We strictly map native fields (name, label) to the internal model
	var req struct {
		Name        string  `json:"name"`
		Label       string  `json:"label"`
		LabelPlural string  `json:"labelPlural"`
		Custom      bool    `json:"custom"`
		Searchable  *bool   `json:"searchable"`
		Description string  `json:"description"`
		Icon        string  `json:"icon"`
		AppID       *string `json:"appId"` // camelCase for appId
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		RespondAppError(c, appErrors.NewValidationError("body", err.Error()))
		return
	}

	// Validation
	if req.Name == "" || req.Label == "" {
		RespondAppError(c, appErrors.NewValidationError("name", "Name and Label are required"))
		return
	}

	searchable := true
	if req.Searchable != nil {
		searchable = *req.Searchable
	}

	// Map to internal model
	schema := &models.ObjectMetadata{
		APIName:     req.Name,
		Label:       req.Label,
		PluralLabel: req.LabelPlural,
		IsCustom:    req.Custom,
		Searchable:  searchable,
		Description: &req.Description,
		Icon:        req.Icon,
		AppID:       req.AppID,
	}

	// Explicitly set IsCustom if true (though usually true for created objects)
	if req.Custom {
		schema.IsCustom = true
	}

	// Use CreateObjectWithApp if AppID is present (delegates to UIAPI for orchestration)
	if schema.AppID != nil && *schema.AppID != "" {
		if err := h.svc.UIAPI.CreateObjectWithApp(c.Request.Context(), *schema.AppID, schema); err != nil {
			RespondAppError(c, err)
			return
		}
	} else {
		if err := h.svc.Metadata.CreateSchema(c.Request.Context(), schema); err != nil {
			RespondAppError(c, err)
			return
		}
		if err := provisionCreatedSchemaPermissions(c.Request.Context(), h.svc, schema); err != nil {
			RespondAppError(c, err)
			return
		}
	}

	// Return standard Salesforce-compatible response
	// We construct a map to ensure we return camelCase keys confirming the creation
	response := map[string]interface{}{
		"name":        schema.APIName,
		"label":       schema.Label,
		"labelPlural": schema.PluralLabel,
		"custom":      schema.IsCustom,
		"id":          schema.ID,
		"success":     true,
	}

	c.JSON(201, response)
}

// UpdateSchema godoc
// @Summary      Update Object Schema
// @Description  Update an existing object definition.
// @Tags         metadata
// @Accept       json
// @Produce      json
// @Param        apiName path string true "Object API Name"
// @Param        updates body models.ObjectMetadata true "Schema Updates"
// @Success      200  {object}  models.ObjectMetadata
// @Failure      400  {object}  map[string]interface{}
// @Router       /metadata/objects/{apiName} [patch]
// @Security     BearerAuth
func (h *MetadataHandler) UpdateSchema(c *gin.Context) {
	// requireSystemAdmin handled by middleware

	apiName := strings.ToLower(c.Param("apiName"))
	if apiName == "" {
		apiName = strings.ToLower(c.Param("id")) // Tooling API uses :id
	}
	var updates models.ObjectMetadata
	if err := c.ShouldBindJSON(&updates); err != nil {
		RespondAppError(c, appErrors.NewValidationError("body", err.Error()))
		return
	}

	if err := h.svc.Metadata.UpdateSchema(c.Request.Context(), apiName, &updates); err != nil {
		RespondAppError(c, err)
		return
	}

	h.svc.UIAPI.InvalidateCaches()

	// Return updated schema
	c.JSON(200, updates)
}

// RefreshCache godoc
// @Summary      Refresh Metadata Cache
// @Description  Reload metadata and dependent caches in the running backend process.
// @Tags         metadata
// @Accept       json
// @Produce      json
// @Success      200  {object}  map[string]interface{}
// @Failure      500  {object}  map[string]interface{}
// @Router       /metadata/refresh-cache [post]
// @Security     BearerAuth
func (h *MetadataHandler) RefreshCache(c *gin.Context) {
	if h.svc == nil {
		RespondAppError(c, appErrors.NewInternalError("service manager not configured", nil))
		return
	}

	if err := h.svc.RefreshMetadataCache(c.Request.Context()); err != nil {
		RespondAppError(c, err)
		return
	}

	c.JSON(200, gin.H{
		"success": true,
		"message": "Metadata caches refreshed",
	})
}

// DeleteSchema godoc
// @Summary      Delete Object Schema
// @Description  Delete a custom object and all associated data.
// @Tags         metadata
// @Accept       json
// @Produce      json
// @Param        apiName path string true "Object API Name"
// @Success      204  "No Content"
// @Failure      400  {object}  map[string]interface{}
// @Router       /metadata/objects/{apiName} [delete]
// @Security     BearerAuth
func (h *MetadataHandler) DeleteSchema(c *gin.Context) {
	// requireSystemAdmin handled by middleware

	apiName := strings.ToLower(c.Param("apiName"))
	if apiName == "" {
		apiName = strings.ToLower(c.Param("id")) // Tooling API uses :id
	}

	// Step 1: Drop table (DDL - auto-commits, cannot be in transaction)
	if err := h.svc.Metadata.DeleteSchema(c.Request.Context(), apiName); err != nil {
		RespondAppError(c, err)
		return
	}

	// Step 2: Cleanup metadata (DML - in transaction for atomicity)
	// Note: Permissions and layouts are auto-deleted via CASCADE foreign keys
	// We only need to manually clean up app navigation items
	err := h.svc.TxManager.WithTransaction(c.Request.Context(), func(txCtx context.Context) error {
		tx := h.svc.TxManager.ExtractTx(txCtx)
		if tx == nil {
			return appErrors.NewInternalError("failed to extract transaction", nil)
		}

		// Remove object from all app navigation items
		if err := h.svc.UIAPI.RemoveObjectFromAllAppsTx(txCtx, tx, apiName); err != nil {
			log.Warn().Err(err).Str("object", apiName).Msg("Failed to remove object from app navigation")
			// Don't fail the entire deletion if this cleanup fails
			// The object is already deleted from DB, this is just UI cleanup
		}
		return nil
	})

	if err != nil {
		log.Error().Err(err).Str("object", apiName).Msg("Critical: Object deleted but post-deletion cleanup failed")
		// We still return success for the schema deletion itself as it's irreversible at this point
	}

	c.Status(204)
}
