package rest

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/api"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// FolderResponse represents a folder in Salesforce wire format
type FolderResponse struct {
	ID               string `json:"id"`
	Name             string `json:"name"`
	Label            string `json:"label"`
	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"`
	CreatedDate      string `json:"createdDate,omitempty"`
	LastModifiedDate string `json:"lastModifiedDate,omitempty"`
}

// toFolderResponse transforms a SObject to FolderResponse
func toFolderResponse(r models.SObject) FolderResponse {
	name := getStringField(r, "Name")
	folderType := getStringField(r, "Type")
	if folderType == "" {
		folderType = "Report" // Default
	}
	accessType := getStringField(r, "AccessType")
	if accessType == "" {
		accessType = "Hidden" // Default to most restrictive
	}

	return FolderResponse{
		ID:               getStringField(r, "Id"),
		Name:             name,
		Label:            name,
		DeveloperName:    getStringField(r, "DeveloperName"),
		Description:      getStringField(r, "Description"),
		Type:             folderType,
		AccessType:       accessType,
		ParentFolderID:   getStringField(r, "ParentId"),
		CreatedDate:      getStringField(r, "CreatedDate"),
		LastModifiedDate: getStringField(r, "LastModifiedDate"),
	}
}

// validateID checks if the provided ID is a non-empty, reasonable format.
// Accepts UUID, Salesforce-style 15/18-char IDs, and any alphanumeric+underscore+hyphen ID.
// The persistence layer handles FK integrity — this is a basic sanity check only.
func validateID(id string) bool {
	if len(id) == 0 || len(id) > 255 {
		return false
	}
	for _, c := range id {
		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_') {
			return false
		}
	}
	return true
}

// normalizeEnum maps a case-insensitive input to its canonical form.
// If the lowercased input is not found in mapping, fallback is returned.
func normalizeEnum(input string, mapping map[string]string, fallback string) string {
	if v, ok := mapping[strings.ToLower(input)]; ok {
		return v
	}
	return fallback
}

// Enum mappings for folder-related types.
var (
	folderTypeMapping = map[string]string{
		"dashboard": "Dashboard",
		"report":    "Report",
	}
	accessTypeMapping = map[string]string{
		"hidden": "Hidden",
		"public": "Public",
		"shared": "Shared",
	}
	sharedToTypeMapping = map[string]string{
		"user":                 "User",
		"group":                "Group",
		"role":                 "Role",
		"roleandsubordinates":  "RoleAndSubordinates",
		"organization":         "Organization",
	}
	accessLevelMapping = map[string]string{
		"manage": "Manage",
		"view":   "View",
	}
)

// normalizeType validates and normalizes folder type
func normalizeType(t string) string {
	return normalizeEnum(t, folderTypeMapping, "Report")
}

// normalizeAccessType validates and normalizes access type
func normalizeAccessType(a string) string {
	return normalizeEnum(a, accessTypeMapping, "Hidden")
}

// normalizeSharedToType validates and normalizes shared to type
func normalizeSharedToType(t string) string {
	return normalizeEnum(t, sharedToTypeMapping, "User")
}

// normalizeAccessLevel validates and normalizes access level for shares
func normalizeAccessLevel(a string) string {
	return normalizeEnum(a, accessLevelMapping, "View")
}

// checkFolderTypePermission verifies the user has the correct per-type system permission
// for write operations. Report folders require ManageReportsInPubFolders, Dashboard folders
// require ManageDashbdsInPubFolders. Returns true if permitted or if permissions can't be loaded.
func checkFolderTypePermission(c *gin.Context, folderType string) bool {
	// Super-users bypass — permission_loader sets empty map for them
	if userVal, ok := c.Get(constants.ContextKeyUser); ok {
		if u, ok := userVal.(auth.UserSession); ok && u.IsSuperUser() {
			return true
		}
	}
	sysPermsVal, exists := c.Get("system_permissions")
	if !exists {
		return true // no permissions loaded — let downstream checks handle it
	}
	perms, ok := sysPermsVal.(map[string]bool)
	if !ok {
		return true
	}
	switch folderType {
	case "Dashboard":
		return perms["ManageDashbdsInPubFolders"]
	default: // Report, Email, etc.
		return perms["ManageReportsInPubFolders"]
	}
}

// folderExists checks if a folder with the given ID exists and is accessible to the user
func (h *FolderHandler) folderExists(ctx context.Context, folderID string, user *models.UserSession) bool {
	_, err := h.svcMgr.Persistence.Retrieve(ctx, folderTableName, folderID, user)
	return err == nil
}

// toFolderShareResponse transforms a SObject to FolderShareResponse.
// Used by legacy code paths and tests; new code should use folderShareRowToResponse.
func toFolderShareResponse(r models.SObject) FolderShareResponse {
	return FolderShareResponse{
		ID:           getStringField(r, "Id"),
		SharedToID:   getStringField(r, "SharedToId"),
		SharedToType: getStringField(r, "SharedToType"),
		AccessLevel:  getStringField(r, "AccessLevel"),
	}
}

// shareBelongsToFolder checks if a share belongs to the specified folder
func shareBelongsToFolder(share models.SObject, folderID string) bool {
	shareFolderID, ok := share["FolderId"].(string)
	return ok && shareFolderID == folderID
}

// parsePaginationParams extracts page and pageSize from query params
func parsePaginationParams(c *gin.Context) (page, pageSize int) {
	page = 0
	pageSize = defaultPageSize
	if pageStr := c.Query("page"); pageStr != "" {
		if p, err := strconv.Atoi(pageStr); err == nil && p >= 0 {
			page = p
		}
	}
	if pageSizeStr := c.Query("pageSize"); pageSizeStr != "" {
		if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
			pageSize = ps
			if pageSize > maxPageSize {
				pageSize = maxPageSize
			}
		}
	}
	return page, pageSize
}

// paginateRecords applies pagination to a slice of records
func paginateRecords(records []models.SObject, page, pageSize int) ([]models.SObject, bool) {
	start := page * pageSize
	end := start + pageSize
	hasNextPage := false

	if start >= len(records) {
		return []models.SObject{}, false
	}
	if end > len(records) {
		end = len(records)
	} else {
		hasNextPage = true
	}
	return records[start:end], hasNextPage
}

// ensurePersonalFolders creates personal Report and Dashboard folders for the user
// if they don't exist yet. Errors are logged but don't block the request (best-effort).
func (h *FolderHandler) ensurePersonalFolders(ctx context.Context, user *models.UserSession) {
	for _, folderType := range []string{"Report", "Dashboard"} {
		result, err := h.svcMgr.QuerySvc.Query(ctx, api.QueryRequest{
			ObjectAPIName: folderTableName,
			Criteria: []api.QueryCriterion{
				{Field: "CreatedById", Op: "=", Val: user.ID},
				{Field: "AccessType", Op: "=", Val: "Hidden"},
				{Field: "Type", Op: "=", Val: folderType},
			},
			Limit: 1,
		}, user)
		if err != nil {
			log.Warn().Err(err).Str("userId", user.ID).Str("type", folderType).Msg("ensurePersonalFolders: failed to query existing folders")
			continue
		}
		if len(result.Records) > 0 {
			// Personal folder of this type already exists
			continue
		}

		var name, developerName string
		if folderType == "Report" {
			name = "My Personal Custom Reports"
			developerName = fmt.Sprintf("my_personal_reports_%s", user.ID)
		} else {
			name = "My Personal Dashboards"
			developerName = fmt.Sprintf("my_personal_dashboards_%s", user.ID)
		}

		record := models.SObject{
			"Name":          name,
			"DeveloperName": developerName,
			"Type":          folderType,
			"AccessType":    "Hidden",
			"CreatedById":   user.ID,
			"OwnerId":       user.ID,
		}
		if _, err := h.svcMgr.Persistence.Insert(ctx, folderTableName, record, user); err != nil {
			log.Warn().Err(err).Str("userId", user.ID).Str("type", folderType).Msg("ensurePersonalFolders: failed to create personal folder")
		}
	}
}
