package rest

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

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

// Tooling API - FlowDefinition CRUD

// ListFlowDefinitions handles GET /tooling/sobjects/FlowDefinition
func (h *FlowHandler) ListFlowDefinitions(c *gin.Context) {
	MustGetUser(c)

	ctx := c.Request.Context()
	flowDefs := h.metadata.GetFlowDefinitions(ctx)

	c.JSON(http.StatusOK, gin.H{
		"done":      true,
		"totalSize": len(flowDefs),
		"records":   flowDefs,
	})
}

// GetFlowDefinition handles GET /tooling/sobjects/FlowDefinition/:id
func (h *FlowHandler) GetFlowDefinition(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	flowDef := h.metadata.GetFlowDefinitionById(ctx, id)
	if flowDef == nil {
		RespondAppError(c, apierr.NotFound("FlowDefinition"))
		return
	}

	c.JSON(http.StatusOK, flowDef)
}

// CreateFlowDefinition handles POST /tooling/sobjects/FlowDefinition
func (h *FlowHandler) CreateFlowDefinition(c *gin.Context) {
	MustGetUser(c)

	var req models.FlowDefinition
	if !BindJSON(c, &req) {
		return
	}

	ctx := c.Request.Context()

	if err := h.metadata.CreateFlowDefinition(ctx, &req); err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":      req.Id,
		"success": true,
		"errors":  []interface{}{},
	})
}

// UpdateFlowDefinition handles PATCH /tooling/sobjects/FlowDefinition/:id
//
// Salesforce Tooling API supports two PATCH shapes for FlowDefinition:
//  1. { Metadata: { activeVersionNumber: N } }          — activate version N (or deactivate if 0)
//  2. { Metadata: { masterLabel, description } }        — update subscriber-editable properties
//  3. { DeveloperName: "...", MasterLabel: "...", ... } — sparse field patch
//
// The handler detects which shape was sent and routes accordingly.
func (h *FlowHandler) UpdateFlowDefinition(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	// Read raw body to inspect shape before unmarshalling
	var raw json.RawMessage
	if err := c.ShouldBindJSON(&raw); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid JSON body"))
		return
	}

	// Check for Salesforce-style Metadata envelope first.
	var wrapper struct {
		Metadata *struct {
			ActiveVersionNumber *int    `json:"activeVersionNumber"`
			MasterLabel         *string `json:"masterLabel"`
			Description         *string `json:"description"`
		} `json:"Metadata"`
	}
	if err := json.Unmarshal(raw, &wrapper); err == nil && wrapper.Metadata != nil && wrapper.Metadata.ActiveVersionNumber != nil {
		activeVersionNum := *wrapper.Metadata.ActiveVersionNumber

		if activeVersionNum <= 0 {
			// Deactivate: find the currently active version and deactivate it
			def := h.metadata.GetFlowDefinitionById(ctx, id)
			if def == nil {
				RespondAppError(c, apierr.NotFound("FlowDefinition"))
				return
			}
			if def.ActiveVersionId == "" {
				// Already inactive — nothing to do
				c.Status(http.StatusNoContent)
				return
			}
			if err := h.metadata.DeactivateFlowVersion(ctx, def.ActiveVersionId); err != nil {
				RespondAppError(c, apierr.Internal(err))
				return
			}
			c.Status(http.StatusNoContent)
			return
		}

		// Activate: find the version by definition ID + version number
		versions, err := h.metadata.GetFlowVersions(ctx, id)
		if err != nil {
			RespondAppError(c, apierr.Internal(err))
			return
		}
		var targetVersionId string
		for _, v := range versions {
			if v.VersionNumber == activeVersionNum {
				targetVersionId = v.Id
				break
			}
		}
		if targetVersionId == "" {
			c.JSON(http.StatusNotFound, apierr.SFErrorResponse(apierr.SFCodeNotFound,
				"Flow version not found for the specified version number"))
			return
		}
		if err := h.metadata.ActivateFlowVersion(ctx, targetVersionId); err != nil {
			RespondAppError(c, apierr.Internal(err))
			return
		}
		c.Status(http.StatusNoContent)
		return
	}

	var patch models.FlowDefinitionPatch
	switch {
	case wrapper.Metadata != nil && (wrapper.Metadata.MasterLabel != nil || wrapper.Metadata.Description != nil):
		patch.MasterLabel = wrapper.Metadata.MasterLabel
		patch.Description = wrapper.Metadata.Description
	case wrapper.Metadata != nil:
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Unsupported FlowDefinition Metadata patch"))
		return
	default:
		if err := json.Unmarshal(raw, &patch); err != nil {
			c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "Invalid FlowDefinition fields"))
			return
		}
	}

	if patch.Empty() {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidField, "No supported FlowDefinition fields to update"))
		return
	}

	if err := h.metadata.UpdateFlowDefinition(ctx, id, &patch); err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.Status(http.StatusNoContent)
}

// DeleteFlowDefinition handles DELETE /tooling/sobjects/FlowDefinition/:id
func (h *FlowHandler) DeleteFlowDefinition(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	if err := h.metadata.DeleteFlowDefinition(ctx, id); err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.Status(http.StatusNoContent)
}

// Tooling API - Flow Version CRUD

// ListFlowVersions handles GET /tooling/sobjects/Flow
func (h *FlowHandler) ListFlowVersions(c *gin.Context) {
	MustGetUser(c)

	definitionId := c.Query("DefinitionId")
	ctx := c.Request.Context()

	versions, err := h.metadata.GetFlowVersions(ctx, definitionId)
	if err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"done":      true,
		"totalSize": len(versions),
		"records":   versions,
	})
}

// GetFlowVersion handles GET /tooling/sobjects/Flow/:id
func (h *FlowHandler) GetFlowVersion(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	version := h.metadata.GetFlowVersionById(ctx, id)
	if version == nil {
		RespondAppError(c, apierr.NotFound("Flow"))
		return
	}

	c.JSON(http.StatusOK, version)
}

// CreateFlowVersion handles POST /tooling/sobjects/Flow
// If DefinitionId is not provided, a new FlowDefinition is auto-created (Salesforce behavior)
func (h *FlowHandler) CreateFlowVersion(c *gin.Context) {
	MustGetUser(c)

	var req models.FlowVersion
	if !BindJSON(c, &req) {
		return
	}

	// MetadataRaw is populated by JSON binding (json:"Metadata"), but the
	// typed Metadata struct is json:"-".  Hydrate it so code below can
	// access req.Metadata.Label / .Start etc.
	if len(req.MetadataRaw) > 0 {
		_ = json.Unmarshal(req.MetadataRaw, &req.Metadata)
	}

	ctx := c.Request.Context()
	var createdDevName string
	var createdDupName bool
	err := h.metadata.WithToolingWriteTx(ctx, func(txCtx context.Context) error {
		if req.DefinitionId == "" {
			label := "New Flow"
			developerName := "New_Flow"
			processType := req.ProcessType
			if processType == "" {
				processType = "AutoLaunchedFlow"
			}

			if req.Metadata.Label != "" {
				label = req.Metadata.Label
				developerName = sanitizeDeveloperName(label)
			}

			developerName = fmt.Sprintf("%s_%d", developerName, time.Now().UnixMilli())
			createdDevName = developerName

			flowDef := &models.FlowDefinition{
				MasterLabel:   label,
				DeveloperName: developerName,
				ProcessType:   processType,
				Description:   req.Metadata.Description,
			}
			if req.Metadata.Start != nil {
				flowDef.TriggerObjectOrEvent = req.Metadata.Start.Object
				flowDef.TriggerType = req.Metadata.Start.TriggerType
			}

			if err := h.metadata.CreateFlowDefinition(txCtx, flowDef); err != nil {
				if isDuplicateKeyError(err) {
					createdDupName = true
				}
				return err
			}
			req.DefinitionId = flowDef.Id
		}
		return h.metadata.CreateFlowVersion(txCtx, &req)
	})
	if err != nil {
		if createdDupName {
			c.JSON(http.StatusConflict, apierr.SFErrorResponse(
				"DUPLICATE_DEVELOPER_NAME",
				fmt.Sprintf("A flow with DeveloperName '%s' already exists. Please use a different name.", createdDevName),
			))
			return
		}
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":            req.Id,
		"success":       true,
		"errors":        []interface{}{},
		"VersionNumber": req.VersionNumber,
	})
}

// isDuplicateKeyError returns true if the error is a MySQL/TiDB duplicate key error (1062).
func isDuplicateKeyError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "Duplicate entry")
}

// sanitizeDeveloperName converts a label to a valid Salesforce DeveloperName
func sanitizeDeveloperName(label string) string {
	// Replace spaces with underscores, remove non-alphanumeric chars (except underscore)
	result := ""
	for _, r := range label {
		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
			result += string(r)
		} else if r == ' ' || r == '-' {
			result += "_"
		}
	}
	if result == "" {
		result = "Flow"
	}
	return result
}

// UpdateFlowVersion handles PATCH /tooling/sobjects/Flow/:id
// Salesforce v63.0 compatible: supports Status field for activation/deactivation
// Example: PATCH /tooling/sobjects/Flow/:id {"Status": "Active"}
func (h *FlowHandler) UpdateFlowVersion(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	// Parse request body to check for Status field (Salesforce v63.0 activation pattern)
	var rawReq map[string]interface{}
	if err := json.NewDecoder(c.Request.Body).Decode(&rawReq); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeJSONParserError, "Invalid JSON"))
		return
	}

	// Handle Status field for activation/deactivation (Salesforce v63.0 compatible)
	if status, ok := rawReq["Status"].(string); ok {
		switch status {
		case "Active":
			if err := h.metadata.ActivateFlowVersion(ctx, id); err != nil {
				RespondAppError(c, apierr.Internal(err))
				return
			}
			c.Status(http.StatusNoContent)
			return
		case "Draft", "Obsolete":
			if err := h.metadata.DeactivateFlowVersion(ctx, id); err != nil {
				RespondAppError(c, apierr.Internal(err))
				return
			}
			c.Status(http.StatusNoContent)
			return
		}
	}

	// Standard update path for other fields
	var req models.FlowVersion
	// Re-encode the map to JSON and decode into struct
	reqBytes, _ := json.Marshal(rawReq)
	if err := json.Unmarshal(reqBytes, &req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, err.Error()))
		return
	}
	// Hydrate typed Metadata from MetadataRaw (json:"-" means Unmarshal skips the typed field)
	if len(req.MetadataRaw) > 0 {
		_ = json.Unmarshal(req.MetadataRaw, &req.Metadata)
	}

	if err := h.metadata.UpdateFlowVersion(ctx, id, &req); err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.Status(http.StatusNoContent)
}

// DeleteFlowVersion handles DELETE /tooling/sobjects/Flow/:id
func (h *FlowHandler) DeleteFlowVersion(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	if err := h.metadata.DeleteFlowVersion(ctx, id); err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.Status(http.StatusNoContent)
}

// ActivateFlowVersion handles POST /tooling/sobjects/Flow/:id/activate
func (h *FlowHandler) ActivateFlowVersion(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	if err := h.metadata.ActivateFlowVersion(ctx, id); err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.Status(http.StatusNoContent)
}

// DeactivateFlowVersion handles POST /tooling/sobjects/Flow/:id/deactivate
func (h *FlowHandler) DeactivateFlowVersion(c *gin.Context) {
	MustGetUser(c)

	id := c.Param("id")
	ctx := c.Request.Context()

	if err := h.metadata.DeactivateFlowVersion(ctx, id); err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	c.Status(http.StatusNoContent)
}
