package rest

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/features/permissions"
	"github.com/nexuscrm/backend/pkg/apierr"
	"github.com/nexuscrm/shared/pkg/api"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// CreateFolderRequest represents a folder creation request
type CreateFolderRequest struct {
	Name           string `json:"name" binding:"required"`
	DeveloperName  string `json:"developerName"`
	Description    string `json:"description,omitempty"`
	Type           string `json:"type"`       // Report or Dashboard
	AccessType     string `json:"accessType"` // Hidden, Public, Shared
	ParentFolderID string `json:"parentFolderId,omitempty"`
}

// UpdateFolderRequest represents a folder update request
type UpdateFolderRequest struct {
	Name           *string `json:"name,omitempty"`
	DeveloperName  *string `json:"developerName,omitempty"`
	Description    *string `json:"description,omitempty"`
	AccessType     *string `json:"accessType,omitempty"`
	ParentFolderID *string `json:"parentFolderId,omitempty"`
}

// ListFolders handles GET /folders
// Query params: filterGroup (Dashboards|Reports), page (default 0), pageSize (default 200, max 2000)
func (h *FolderHandler) ListFolders(c *gin.Context) {
	user := MustGetUser(c)

	// Lazy-create personal folders if user doesn't have any yet.
	// This runs on first ListFolders call per user — avoids injecting
	// folder creation into AuthService (which lacks persistence deps).
	if h.folderAccess != nil {
		h.ensurePersonalFolders(c.Request.Context(), user)
	}

	// Parse filterGroup query param to filter by folder Type
	filterGroup := c.Query("filterGroup") // "Dashboards" or "Reports"

	page, pageSize := parsePaginationParams(c)

	ctx := c.Request.Context()

	// Determine which folders the user is allowed to see
	var visibleIDs []string
	if h.folderAccess != nil {
		var err error
		visibleIDs, err = h.folderAccess.VisibleFolderIDs(ctx, user)
		if err != nil {
			c.JSON(http.StatusOK, gin.H{"folders": []interface{}{}, "hasNextPage": false})
			return
		}
		if visibleIDs != nil && len(visibleIDs) == 0 {
			// User has no folder access at all
			c.JSON(http.StatusOK, gin.H{"folders": []interface{}{}, "hasNextPage": false})
			return
		}
	}

	// Use direct SQL query since QueryWithFilter has issues parsing filters on system_metadata tables
	var result *models.QueryResult
	var err error

	if filterGroup == "Dashboards" || filterGroup == "Reports" {
		// Map filterGroup to actual Type value
		folderType := "Report"
		if filterGroup == "Dashboards" {
			folderType = "Dashboard"
		}
		// Query with type filter using direct persistence query method
		result, err = h.svcMgr.QuerySvc.QueryWithFilter(ctx, folderTableName, "", user, "Name", "ASC", maxQueryLimit)
		if err != nil {
			RespondAppError(c, err)
			return
		}
		// Filter in memory since the expression parser has issues with this table
		var filtered []models.SObject
		for _, record := range result.Records {
			if typeVal, ok := record["Type"].(string); ok && typeVal == folderType {
				filtered = append(filtered, record)
			}
		}
		result.Records = filtered
		result.TotalSize = len(filtered)
	} else {
		// No filter - get all folders
		result, err = h.svcMgr.QuerySvc.QueryWithFilter(ctx, folderTableName, "", user, "Name", "ASC", maxQueryLimit)
		if err != nil {
			RespondAppError(c, err)
			return
		}
	}

	// If visibleIDs is non-nil (non-admin), filter results to only visible folders
	if visibleIDs != nil {
		visibleSet := make(map[string]struct{}, len(visibleIDs))
		for _, id := range visibleIDs {
			visibleSet[id] = struct{}{}
		}
		filtered := result.Records[:0]
		for _, r := range result.Records {
			if id, ok := r["Id"].(string); ok {
				if _, visible := visibleSet[id]; visible {
					filtered = append(filtered, r)
				}
			}
		}
		result.Records = filtered
		result.TotalSize = len(filtered)
	}

	paginatedRecords, hasNextPage := paginateRecords(result.Records, page, pageSize)

	folders := make([]FolderResponse, 0, len(paginatedRecords))
	for _, r := range paginatedRecords {
		folders = append(folders, toFolderResponse(r))
	}

	response := gin.H{"folders": folders, "hasNextPage": hasNextPage}
	if hasNextPage {
		nextPageUrl := fmt.Sprintf("/services/data/v63.0/folders?page=%d&pageSize=%d", page+1, pageSize)
		if filterGroup != "" {
			nextPageUrl += "&filterGroup=" + url.QueryEscape(filterGroup)
		}
		response["nextPageUrl"] = nextPageUrl
	}

	c.JSON(http.StatusOK, response)
}

// GetFolder handles GET /folders/:id
func (h *FolderHandler) GetFolder(c *gin.Context) {
	user := MustGetUser(c)

	folderID, ok := RequireParam(c, "id")
	if !ok {
		return
	}

	if !validateID(folderID) {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid folder ID format"))
		return
	}

	ctx := c.Request.Context()

	if h.folderAccess != nil && !h.folderAccess.CheckAccess(ctx, user, folderID, permissions.FolderAccessView) {
		c.JSON(http.StatusNotFound, gin.H{"errorCode": "NOT_FOUND", "message": fmt.Sprintf("Folder %s not found or access denied", folderID)})
		return
	}

	record, err := h.svcMgr.Persistence.Retrieve(ctx, folderTableName, folderID, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	c.JSON(http.StatusOK, toFolderResponse(record))
}

// GetFolderChildren handles GET /folders/:id/children
// Query params: page (default 0), pageSize (default 200, max 2000)
func (h *FolderHandler) GetFolderChildren(c *gin.Context) {
	user := MustGetUser(c)

	parentID, ok := RequireParam(c, "id")
	if !ok {
		return
	}

	if !validateID(parentID) {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid folder ID format"))
		return
	}

	page, pageSize := parsePaginationParams(c)
	ctx := c.Request.Context()

	if h.folderAccess != nil && !h.folderAccess.CheckAccess(ctx, user, parentID, permissions.FolderAccessView) {
		c.JSON(http.StatusNotFound, gin.H{"errorCode": "NOT_FOUND", "message": fmt.Sprintf("Folder %s not found or access denied", parentID)})
		return
	}

	// Check parent folder exists
	if !h.folderExists(ctx, parentID, user) {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Parent folder not found"))
		return
	}

	result, err := h.svcMgr.QuerySvc.Query(ctx, api.QueryRequest{
		ObjectAPIName: folderTableName,
		Criteria:      []api.QueryCriterion{{Field: "ParentId", Op: "=", Val: parentID}},
		SortField:     "Name",
		SortDirection: "ASC",
		Limit:         maxQueryLimit,
	}, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	paginatedRecords, hasNextPage := paginateRecords(result.Records, page, pageSize)

	folders := make([]FolderResponse, 0, len(paginatedRecords))
	for _, r := range paginatedRecords {
		folders = append(folders, toFolderResponse(r))
	}

	response := gin.H{"folders": folders, "hasNextPage": hasNextPage}
	if hasNextPage {
		response["nextPageUrl"] = fmt.Sprintf("/services/data/v63.0/folders/%s/children?page=%d&pageSize=%d", parentID, page+1, pageSize)
	}

	c.JSON(http.StatusOK, response)
}

// CreateFolder handles POST /folders
func (h *FolderHandler) CreateFolder(c *gin.Context) {
	user := MustGetUser(c)

	var req CreateFolderRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeRequiredFieldMissing, err.Error()))
		return
	}

	// Generate DeveloperName if not provided
	developerName := req.DeveloperName
	if developerName == "" {
		developerName = strings.ReplaceAll(req.Name, " ", "_")
	}

	// Validate and normalize Type
	folderType := normalizeType(req.Type)

	// Check per-type system permission
	if !user.IsSuperUser() {
		sysPermsVal, _ := c.Get(constants.ContextKeySystemPermissions)
		sysPerms, _ := sysPermsVal.(map[string]bool)
		switch folderType {
		case "Dashboard":
			if !sysPerms[string(constants.PermCreateDashboards)] {
				c.JSON(http.StatusForbidden, gin.H{"errorCode": "INSUFFICIENT_ACCESS", "message": "You do not have permission to create dashboards"})
				return
			}
		case "Report":
			if !sysPerms[string(constants.PermCreateCustomizeReports)] {
				c.JSON(http.StatusForbidden, gin.H{"errorCode": "INSUFFICIENT_ACCESS", "message": "You do not have permission to create reports"})
				return
			}
		}
	}

	// Check per-type folder management permission (Report vs Dashboard)
	if !user.IsSuperUser() && !checkFolderTypePermission(c, folderType) {
		c.JSON(http.StatusForbidden, gin.H{"errorCode": "FORBIDDEN", "message": "You do not have permission to manage this folder type"})
		return
	}

	// Validate and normalize AccessType
	accessType := normalizeAccessType(req.AccessType)

	ctx := c.Request.Context()
	data := models.SObject{
		"Name":          req.Name,
		"DeveloperName": developerName,
		"Type":          folderType,
		"AccessType":    accessType,
	}
	if req.Description != "" {
		data["Description"] = req.Description
	}
	if req.ParentFolderID != "" {
		if !validateID(req.ParentFolderID) {
			c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid parent folder ID format"))
			return
		}
		// Validate parent folder exists
		if !h.folderExists(ctx, req.ParentFolderID, user) {
			c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Parent folder not found"))
			return
		}
		data["ParentId"] = req.ParentFolderID
	}

	createdRecord, err := h.svcMgr.Persistence.Insert(ctx, folderTableName, data, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	// Public folders are visible org-wide via an Organization View share.
	// Shared folders remain visible only through explicit non-Organization shares.
	// If share creation fails, delete the folder to prevent an inconsistent state
	// where AccessType=Public but no Organization share exists.
	if h.folderAccess != nil && accessType == "Public" {
		folderID := ""
		if id, ok := createdRecord["Id"].(string); ok {
			folderID = id
		}
		if folderID != "" {
			if shareErr := h.folderAccess.EnsureOrganizationViewShare(ctx, folderID, user.ID); shareErr != nil {
				// Rollback: delete the folder we just created
				if delErr := h.svcMgr.Persistence.Delete(ctx, folderTableName, folderID, user); delErr != nil {
					log.Error().Err(delErr).Str("folderId", folderID).Msg("CreateFolder: share failed AND rollback delete failed — orphaned folder")
				}
				c.JSON(http.StatusInternalServerError, gin.H{"errorCode": "SHARE_SYNC_FAILED", "message": "Folder creation failed: could not create Organization share"})
				return
			}
		}
	}

	c.JSON(http.StatusCreated, toFolderResponse(createdRecord))
}

// UpdateFolder handles PATCH /folders/:id
func (h *FolderHandler) UpdateFolder(c *gin.Context) {
	user := MustGetUser(c)

	folderID, ok := RequireParam(c, "id")
	if !ok {
		return
	}

	if !validateID(folderID) {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid folder ID format"))
		return
	}

	var req UpdateFolderRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, err.Error()))
		return
	}

	ctx := c.Request.Context()

	if h.folderAccess != nil && !h.folderAccess.CheckAccess(ctx, user, folderID, permissions.FolderAccessManage) {
		c.JSON(http.StatusNotFound, gin.H{"errorCode": "NOT_FOUND", "message": fmt.Sprintf("Folder %s not found or access denied", folderID)})
		return
	}

	// Check per-type folder management permission (Report vs Dashboard)
	if h.folderAccess != nil {
		if !checkFolderTypePermission(c, h.folderAccess.GetFolderType(ctx, folderID)) {
			c.JSON(http.StatusForbidden, gin.H{"errorCode": "FORBIDDEN", "message": "You do not have permission to manage this folder type"})
			return
		}
	}

	// Check folder exists before updating
	if !h.folderExists(ctx, folderID, user) {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Folder not found"))
		return
	}

	// Name/DeveloperName changes require SuperUser (SF: admin-only)
	if req.Name != nil || req.DeveloperName != nil {
		if !user.IsSystemAdmin && !user.IsSuperUser() {
			c.JSON(http.StatusForbidden, gin.H{
				"errorCode": "INSUFFICIENT_ACCESS",
				"message":   "Only administrators can change folder Name or DeveloperName",
			})
			return
		}
	}

	updates := models.SObject{}
	if req.Name != nil {
		updates["Name"] = *req.Name
	}
	if req.DeveloperName != nil {
		updates["DeveloperName"] = *req.DeveloperName
	}
	if req.Description != nil {
		updates["Description"] = *req.Description
	}
	// Track AccessType change for post-update share sync
	var oldAccessType, newAccessType string
	if req.AccessType != nil {
		newAccessType = normalizeAccessType(*req.AccessType)
		updates["AccessType"] = newAccessType
		// Read current AccessType before update
		if h.folderAccess != nil {
			if rec, rerr := h.svcMgr.Persistence.Retrieve(ctx, folderTableName, folderID, user); rerr == nil {
				if v, ok := rec["AccessType"].(string); ok {
					oldAccessType = v
				}
			}
		}
	}
	if req.ParentFolderID != nil {
		if *req.ParentFolderID != "" && !validateID(*req.ParentFolderID) {
			c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid parent folder ID format"))
			return
		}
		// Validate parent folder exists (if not clearing)
		if *req.ParentFolderID != "" && !h.folderExists(ctx, *req.ParentFolderID, user) {
			c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Parent folder not found"))
			return
		}
		updates["ParentId"] = *req.ParentFolderID
	}

	if len(updates) == 0 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeRequiredFieldMissing, "No fields to update"))
		return
	}

	err := h.svcMgr.Persistence.Update(ctx, folderTableName, folderID, updates, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	// Sync FolderShare AFTER successful update — if share sync fails, revert AccessType
	// to prevent drift between declared AccessType and actual visibility.
	if h.folderAccess != nil && newAccessType != "" && oldAccessType != newAccessType {
		var syncErr error
		if newAccessType == "Hidden" || newAccessType == "Shared" {
			// Hidden and Shared both drop the implicit org-wide baseline while preserving
			// targeted User/Group/Role shares.
			if derr := h.folderAccess.DeleteSharesByType(ctx, folderID, "Organization"); derr != nil {
				syncErr = derr
			}
		} else if newAccessType == "Public" {
			if serr := h.folderAccess.EnsureOrganizationViewShare(ctx, folderID, user.ID); serr != nil {
				syncErr = serr
			}
		}
		if syncErr != nil {
			// Revert AccessType to prevent drift
			if revertErr := h.svcMgr.Persistence.Update(ctx, folderTableName, folderID, models.SObject{"AccessType": oldAccessType}, user); revertErr != nil {
				log.Error().Err(revertErr).Str("folderId", folderID).Str("oldAccessType", oldAccessType).Msg("UpdateFolder: share sync failed AND AccessType revert failed — drift state")
			}
			c.JSON(http.StatusInternalServerError, gin.H{"errorCode": "SHARE_SYNC_FAILED", "message": "Folder update reverted: failed to sync folder sharing"})
			return
		}
	}

	record, err := h.svcMgr.Persistence.Retrieve(ctx, folderTableName, folderID, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	c.JSON(http.StatusOK, toFolderResponse(record))
}

// TransferOwnershipRequest is the request body for POST /folders/transfer-ownership.
type TransferOwnershipRequest struct {
	SourceUserID string `json:"sourceUserId" binding:"required"`
	TargetUserID string `json:"targetUserId" binding:"required"`
}

// TransferOwnership handles POST /folders/transfer-ownership.
// Requires admin (SuperUser / IsSystemAdmin) — transfers all ReportFolder rows
// owned by sourceUserId to targetUserId. Used when deactivating a user.
func (h *FolderHandler) TransferOwnership(c *gin.Context) {
	user := MustGetUser(c)

	if !user.IsSystemAdmin && !user.IsSuperUser() {
		c.JSON(http.StatusForbidden, apierr.SFErrorResponse(apierr.SFCodeForbidden, "Admin permission required to transfer folder ownership"))
		return
	}

	var req TransferOwnershipRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeRequiredFieldMissing, err.Error()))
		return
	}

	if !validateID(req.SourceUserID) || !validateID(req.TargetUserID) {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid user ID format"))
		return
	}

	if req.SourceUserID == req.TargetUserID {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "sourceUserId and targetUserId must be different"))
		return
	}

	if h.folderAccess == nil {
		c.JSON(http.StatusInternalServerError, apierr.SFErrorResponse(apierr.SFCodeInternalError, "Folder access service unavailable"))
		return
	}

	if err := h.folderAccess.TransferFolderOwnership(c.Request.Context(), req.SourceUserID, req.TargetUserID); err != nil {
		c.JSON(http.StatusInternalServerError, apierr.SFErrorResponse(apierr.SFCodeInternalError, "Failed to transfer folder ownership"))
		return
	}

	c.JSON(http.StatusOK, gin.H{"success": true, "sourceUserId": req.SourceUserID, "targetUserId": req.TargetUserID})
}

// DeleteFolder handles DELETE /folders/:id
func (h *FolderHandler) DeleteFolder(c *gin.Context) {
	user := MustGetUser(c)

	folderID, ok := RequireParam(c, "id")
	if !ok {
		return
	}

	if !validateID(folderID) {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid folder ID format"))
		return
	}

	ctx := c.Request.Context()

	if h.folderAccess != nil && !h.folderAccess.CheckAccess(ctx, user, folderID, permissions.FolderAccessManage) {
		c.JSON(http.StatusNotFound, gin.H{"errorCode": "NOT_FOUND", "message": fmt.Sprintf("Folder %s not found or access denied", folderID)})
		return
	}

	// Check per-type folder management permission (Report vs Dashboard)
	if h.folderAccess != nil {
		if !checkFolderTypePermission(c, h.folderAccess.GetFolderType(ctx, folderID)) {
			c.JSON(http.StatusForbidden, gin.H{"errorCode": "FORBIDDEN", "message": "You do not have permission to manage this folder type"})
			return
		}
	}

	// Check folder exists before deleting
	if !h.folderExists(ctx, folderID, user) {
		c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound, "Folder not found"))
		return
	}

	err := h.svcMgr.Persistence.Delete(ctx, folderTableName, folderID, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	c.Status(http.StatusNoContent)
}
