package rest

import (
	"context"
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/pkg/apierr"
	"github.com/nexuscrm/shared/pkg/constants"
)

// safeID matches alphanumeric IDs with hyphens (UUIDs). Rejects quotes and special chars.
var safeID = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)

// ListViews handles GET /sobjects/:sobject/listviews - returns available list views
func (h *SObjectHandler) ListViews(c *gin.Context) {
	objectName := c.Param("sobject")
	user := MustGetUser(c)
	canRead := h.svcMgr.Permissions.CheckObjectPermissionWithUser(c.Request.Context(), objectName, constants.PermRead, user)
	if !canRead {
		c.JSON(http.StatusForbidden, apierr.SFErrorResponse(apierr.SFCodeForbidden, "Insufficient access"))
		return
	}

	schema := h.svcMgr.Metadata.GetSchema(c.Request.Context(), objectName)
	if schema == nil {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Object not found"))
		return
	}

	allID := fmt.Sprintf("%s-all-view", objectName)
	myID := fmt.Sprintf("%s-my-view", objectName)
	recentID := fmt.Sprintf("%s-recent-view", objectName)

	listViews := []gin.H{
		{
			"id":             allID,
			"label":          "All " + schema.PluralLabel,
			"developerName":  "All",
			"isDefault":      "All" == constants.ListViewDefaultDeveloperName,
			"describeUrl":    fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s/describe", objectName, allID),
			"resultsUrl":     fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s/results", objectName, allID),
			"soqlCompatible": true,
			"url":            fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s", objectName, allID),
		},
		{
			"id":             myID,
			"label":          "My " + schema.PluralLabel,
			"developerName":  "My",
			"isDefault":      "My" == constants.ListViewDefaultDeveloperName,
			"describeUrl":    fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s/describe", objectName, myID),
			"resultsUrl":     fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s/results", objectName, myID),
			"soqlCompatible": true,
			"url":            fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s", objectName, myID),
		},
		{
			"id":             recentID,
			"label":          "Recently Viewed",
			"developerName":  "RecentlyViewed",
			"isDefault":      "RecentlyViewed" == constants.ListViewDefaultDeveloperName,
			"describeUrl":    fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s/describe", objectName, recentID),
			"resultsUrl":     fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s/results", objectName, recentID),
			"soqlCompatible": true,
			"url":            fmt.Sprintf("/services/data/v63.0/sobjects/%s/listviews/%s", objectName, recentID),
		},
	}

	response := gin.H{
		"done":           true,
		"listviews":      listViews,
		"nextRecordsUrl": nil,
		"size":           len(listViews),
		"sobjectType":    objectName,
	}

	c.JSON(http.StatusOK, response)
}

// ListViewDescribe handles GET /sobjects/:sobject/listviews/:listViewId/describe
// Returns the SOQL query for a list view so the frontend can apply filters.
func (h *SObjectHandler) ListViewDescribe(c *gin.Context) {
	objectName := c.Param("sobject")
	listViewId := c.Param("listViewId")
	user := MustGetUser(c)
	canRead := h.svcMgr.Permissions.CheckObjectPermissionWithUser(c.Request.Context(), objectName, constants.PermRead, user)
	if !canRead {
		c.JSON(http.StatusForbidden, apierr.SFErrorResponse(apierr.SFCodeForbidden, "Insufficient access"))
		return
	}

	schema := h.svcMgr.Metadata.GetSchema(c.Request.Context(), objectName)
	if schema == nil {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Object not found"))
		return
	}

	selectFields := "Id, Name"
	allID := fmt.Sprintf("%s-all-view", objectName)
	myID := fmt.Sprintf("%s-my-view", objectName)
	recentID := fmt.Sprintf("%s-recent-view", objectName)

	switch listViewId {
	case allID:
		c.JSON(http.StatusOK, gin.H{
			"id":          listViewId,
			"query":       fmt.Sprintf("SELECT %s FROM %s", selectFields, objectName),
			"sobjectType": objectName,
		})
		return

	case myID:
		c.JSON(http.StatusOK, gin.H{
			"id":          listViewId,
			"query":       fmt.Sprintf("SELECT %s FROM %s WHERE OwnerId = '%s'", selectFields, objectName, user.ID),
			"sobjectType": objectName,
		})
		return

	case recentID:
		soqlQuery := fmt.Sprintf("SELECT %s FROM %s", selectFields, objectName)
		if h.mruService != nil {
			recentIDs, err := h.recentRecordIDs(c.Request.Context(), user.ID, objectName)
			if err != nil {
				c.JSON(http.StatusInternalServerError, apierr.SFErrorResponse(apierr.SFCodeInternalError, "Failed to retrieve recent items"))
				return
			}
			if len(recentIDs) > 0 {
				quoted := make([]string, 0, len(recentIDs))
				for _, id := range recentIDs {
					if safeID.MatchString(id) {
						quoted = append(quoted, "'"+id+"'")
					}
				}
				if len(quoted) > 0 {
					soqlQuery += fmt.Sprintf(" WHERE Id IN (%s)", strings.Join(quoted, ", "))
				} else {
					soqlQuery += " WHERE Id = 'none'"
				}
			} else {
				soqlQuery += " WHERE Id = 'none'"
			}
		} else {
			soqlQuery += " WHERE Id = 'none'"
		}
		soqlQuery += " ORDER BY xMruAccessedAt DESC"
		c.JSON(http.StatusOK, gin.H{
			"id":          listViewId,
			"query":       soqlQuery,
			"sobjectType": objectName,
			"orderBy":     []gin.H{{"field": "xMruAccessedAt", "direction": "DESC"}},
		})
		return

	default:
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "List view not found: "+listViewId))
		return
	}
}

// recentRecordIDs returns up to 200 record IDs from the MRU service for the
// given user and object. Used by ListViewDescribe to build the "Recently Viewed"
// fallback query.
func (h *SObjectHandler) recentRecordIDs(ctx context.Context, userID, objectAPIName string) ([]string, error) {
	items, _, err := h.mruService.GetRelevantItems(ctx, userID, []string{objectAPIName}, "", "v63.0")
	if err != nil {
		return nil, err
	}
	ids := make([]string, 0, len(items))
	for _, item := range items {
		ids = append(ids, item.Id)
	}
	if len(ids) > 200 {
		ids = ids[:200]
	}
	return ids, nil
}

// UpsertByExternalID handles PATCH /sobjects/:sobject/:fieldName/:fieldValue
func (h *SObjectHandler) UpsertByExternalID(c *gin.Context) {
	objectName := c.Param("sobject")
	fieldName := c.Param("fieldName")
	fieldValue := c.Param("fieldValue")
	user := MustGetUser(c)
	ctx := c.Request.Context()

	var body map[string]interface{}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, err.Error()))
		return
	}

	schema := h.svcMgr.Metadata.GetSchema(ctx, objectName)
	if schema == nil {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Object not found"))
		return
	}

	if err := h.validateInput(schema, body); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, err.Error()))
		return
	}

	result, created, err := h.svcMgr.Persistence.UpsertByExternalID(ctx, objectName, fieldName, fieldValue, body, user)
	if err != nil {
		WriteError(c, err, apierr.SFCodeUpsertFailed)
		return
	}

	if created {
		c.JSON(http.StatusCreated, gin.H{
			"id":      result[constants.FieldID],
			"success": true,
			"created": true,
			"errors":  []map[string]interface{}{},
		})
	} else {
		c.Status(http.StatusNoContent)
	}
}

// GetByExternalID handles GET /sobjects/:sobject/:fieldName/:fieldValue
func (h *SObjectHandler) GetByExternalID(c *gin.Context) {
	objectName := c.Param("sobject")
	fieldName := c.Param("fieldName")
	fieldValue := c.Param("fieldValue")
	user := MustGetUser(c)
	ctx := c.Request.Context()

	schema := h.svcMgr.Metadata.GetSchema(ctx, objectName)
	if schema == nil {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Object not found"))
		return
	}

	fieldExists := false
	for _, f := range schema.Fields {
		if f.APIName == fieldName {
			fieldExists = true
			break
		}
	}
	if !fieldExists {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, fmt.Sprintf("Field '%s' does not exist on object '%s'", fieldName, objectName)))
		return
	}

	record, err := h.svcMgr.Persistence.RetrieveByField(ctx, objectName, fieldName, fieldValue, user)
	if err != nil {
		// Canonical classification via RespondError (IsInternal → 500,
		// PermissionError → 403, *apierr.Error{403} → 403). Without this,
		// a permission-denied read would be misreported as 400 and an
		// infra failure (e.g. DB outage) would be hidden as a 4xx
		// business error.
		if RespondError(c, err) {
			return
		}
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeQueryFailed, err.Error()))
		return
	}

	if record == nil {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Record not found"))
		return
	}

	record["attributes"] = gin.H{
		"type": objectName,
		"url":  fmt.Sprintf("/services/data/v63.0/sobjects/%s/%s", objectName, record[constants.FieldID]),
	}

	if h.mruService != nil {
		if recordID, ok := record[constants.FieldID].(string); ok {
			go h.mruService.TrackRecordAccess(context.Background(), user.ID, objectName, recordID)
		}
	}

	c.JSON(http.StatusOK, record)
}
