package rest

import (
	"context"
	"encoding/json"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/features/automation"
	"github.com/nexuscrm/backend/pkg/apierr"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// ApprovalService defines the interface for approval operations
type ApprovalService interface {
	Submit(ctx context.Context, req automation.SubmitRequest, user *models.UserSession) (models.SObject, error)
	Approve(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error)
	Reject(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error)
	Recall(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error)
	Reassign(ctx context.Context, workItemID, newActorID string, user *models.UserSession) error
	ListPendingApprovals(ctx context.Context, user *models.UserSession) ([]automation.PendingApprovalView, error)
	GetLatestProcessInstance(ctx context.Context, recordID string, user *models.UserSession) (*automation.ProcessInstanceSummary, error)
	Clone(ctx context.Context, processDefID, newDeveloperName, newLabel string, user *models.UserSession) (*models.SObject, error)
	ProjectChain(ctx context.Context, processDefID, objectAPIName, recordID string, user *models.UserSession) ([]automation.ProjectedNode, error)
}

// ApprovalHandler handles approval process API endpoints
type ApprovalHandler struct {
	svc ApprovalService
}

// NewApprovalHandler creates a new ApprovalHandler
func NewApprovalHandler(svc ApprovalService) *ApprovalHandler {
	return &ApprovalHandler{svc: svc}
}

// Request/Response Types

// ProcessResult represents a single result in the Salesforce Process Approvals response.
// POST /services/data/v63.0/process/approvals returns an array of these.
type ProcessResult struct {
	ActorIds       []string `json:"actorIds"`
	EntityId       string   `json:"entityId"`
	Errors         []string `json:"errors"`
	InstanceId     string   `json:"instanceId"`
	InstanceStatus string   `json:"instanceStatus"`
	NewWorkitemIds []string `json:"newWorkitemIds"`
	Success        bool     `json:"success"`
}

// Public Endpoints

// SFApprovalRequest represents a single request in the Salesforce Process Approvals API.
// Salesforce wire format: POST /process/approvals with { "requests": [...] }
type SFApprovalRequest struct {
	ActionType                string   `json:"actionType"`                          // "Submit", "Approve", "Reject", "Removed" (Removed = Recall)
	ContextActorId            string   `json:"contextActorId,omitempty"`            // submitter user ID
	ContextId                 string   `json:"contextId"`                           // record ID or work item ID
	Comments                  string   `json:"comments,omitempty"`                  // approval comments
	NextApproverIds           []string `json:"nextApproverIds,omitempty"`           // next approver IDs
	ProcessDefinitionNameOrId string   `json:"processDefinitionNameOrId,omitempty"` // specific process to use
	SkipEntryCriteria         bool     `json:"skipEntryCriteria,omitempty"`         // skip entry criteria check
}

// SFApprovalRequestWrapper is the Salesforce-standard POST body wrapper.
// Salesforce sends: { "requests": [ { ... }, { ... } ] }
type SFApprovalRequestWrapper struct {
	Requests []SFApprovalRequest `json:"requests"`
}

// legacyApprovalRequest is our backward-compatible flat request format (pre-parity).
type legacyApprovalRequest struct {
	ActionType    string `json:"actionType"`
	ContextID     string `json:"contextId"`
	Comments      string `json:"comments"`
	ObjectAPIName string `json:"ObjectApiName"`
	RecordID      string `json:"RecordId"`
}

// HandleProcessApprovals handles POST /services/data/v63.0/process/approvals
// Supports both Salesforce-standard {requests: [...]} and legacy flat request body.
func (h *ApprovalHandler) HandleProcessApprovals(c *gin.Context) {
	user := MustGetUser(c)

	// Try to parse as SF-standard wrapper first
	var rawBody map[string]interface{}
	bodyBytes, err := c.GetRawData()
	if err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeJSONParserError, "Failed to read request body"))
		return
	}

	if err := json.Unmarshal(bodyBytes, &rawBody); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeJSONParserError, "Invalid JSON"))
		return
	}

	var sfRequests []SFApprovalRequest

	if _, hasRequests := rawBody["requests"]; hasRequests {
		// SF-standard format: { "requests": [...] }
		var wrapper SFApprovalRequestWrapper
		if err := json.Unmarshal(bodyBytes, &wrapper); err != nil {
			c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeJSONParserError, "Invalid approval requests format"))
			return
		}
		sfRequests = wrapper.Requests
	} else {
		// Legacy flat format — convert to SF request
		var legacy legacyApprovalRequest
		if err := json.Unmarshal(bodyBytes, &legacy); err != nil {
			c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeJSONParserError, "Invalid approval request format"))
			return
		}
		contextId := legacy.ContextID
		if contextId == "" {
			contextId = legacy.RecordID
		}
		sfRequests = []SFApprovalRequest{{
			ActionType:     legacy.ActionType,
			ContextId:      contextId,
			ContextActorId: user.ID,
			Comments:       legacy.Comments,
		}}
	}

	if len(sfRequests) == 0 {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "At least one approval request is required"))
		return
	}

	ctx := c.Request.Context()
	results := make([]ProcessResult, 0, len(sfRequests))

	for _, req := range sfRequests {
		// Default ActionType to Submit if missing (Salesforce behavior)
		if req.ActionType == "" {
			req.ActionType = "Submit"
		}

		switch req.ActionType {
		case "Submit":
			if req.ContextId == "" {
				results = append(results, ProcessResult{
					Success:        false,
					ActorIds:       []string{},
					NewWorkitemIds: []string{},
					Errors:         []string{"Missing contextId for Submit action"},
				})
				continue
			}

			// For submit, we need to determine the object name.
			// In SF, contextId is the record ID. We look up the object from the record.
			objectName := ""
			if rawBody["ObjectApiName"] != nil {
				objectName, _ = rawBody["ObjectApiName"].(string)
			}

			serviceReq := automation.SubmitRequest{
				ObjectAPIName:     objectName,
				RecordID:          req.ContextId,
				Comments:          req.Comments,
				SkipEntryCriteria: req.SkipEntryCriteria,
			}

			workItem, err := h.svc.Submit(ctx, serviceReq, user)
			if err != nil {
				handleApprovalErrorInBatch(err, req.ContextId, &results)
				continue
			}

			instanceStatus, _ := workItem["InstanceStatus"].(string)
			if instanceStatus == "" {
				instanceStatus = constants.ApprovalStatusPending
			}
			results = append(results, buildProcessResult(workItem, req.ContextId, instanceStatus, user))

		case "Approve":
			if req.ContextId == "" {
				results = append(results, ProcessResult{
					Success:        false,
					ActorIds:       []string{},
					NewWorkitemIds: []string{},
					Errors:         []string{"Missing contextId (WorkItemId) for Approve action"},
				})
				continue
			}
			workItem, err := h.svc.Approve(ctx, req.ContextId, req.Comments, user)
			if err != nil {
				handleApprovalErrorInBatch(err, req.ContextId, &results)
				continue
			}
			// Use actual instance status from service (Pending if advanced to next step, Approved if finalized)
			instanceStatus, _ := workItem["InstanceStatus"].(string)
			if instanceStatus == "" {
				instanceStatus = constants.ApprovalStatusApproved
			}
			results = append(results, buildProcessResult(workItem, req.ContextId, instanceStatus, user))

		case "Reject":
			if req.ContextId == "" {
				results = append(results, ProcessResult{
					Success:        false,
					ActorIds:       []string{},
					NewWorkitemIds: []string{},
					Errors:         []string{"Missing contextId (WorkItemId) for Reject action"},
				})
				continue
			}
			workItem, err := h.svc.Reject(ctx, req.ContextId, req.Comments, user)
			if err != nil {
				handleApprovalErrorInBatch(err, req.ContextId, &results)
				continue
			}
			results = append(results, buildProcessResult(workItem, req.ContextId, constants.ApprovalStatusRejected, user))

		case "Reassign":
			if req.ContextId == "" || len(req.NextApproverIds) == 0 {
				results = append(results, ProcessResult{
					Success:        false,
					ActorIds:       []string{},
					NewWorkitemIds: []string{},
					Errors:         []string{"Missing contextId or nextApproverIds for Reassign action"},
				})
				continue
			}
			err := h.svc.Reassign(ctx, req.ContextId, req.NextApproverIds[0], user)
			if err != nil {
				handleApprovalErrorInBatch(err, req.ContextId, &results)
				continue
			}
			results = append(results, ProcessResult{
				Success:        true,
				ActorIds:       req.NextApproverIds[:1],
				InstanceId:     req.ContextId,
				InstanceStatus: constants.ApprovalStatusPending,
				NewWorkitemIds: []string{req.ContextId},
			})

		case "Removed": // Action type verb (Salesforce wire format), not a status constant
			if req.ContextId == "" {
				results = append(results, ProcessResult{
					Success:        false,
					ActorIds:       []string{},
					NewWorkitemIds: []string{},
					Errors:         []string{"Missing contextId (WorkItemId) for Removed action"},
				})
				continue
			}
			workItem, err := h.svc.Recall(ctx, req.ContextId, req.Comments, user)
			if err != nil {
				handleApprovalErrorInBatch(err, req.ContextId, &results)
				continue
			}
			results = append(results, buildProcessResult(workItem, req.ContextId, constants.ApprovalStatusRemoved, user))

		default:
			results = append(results, ProcessResult{
				Success:        false,
				ActorIds:       []string{},
				NewWorkitemIds: []string{},
				Errors:         []string{"Unsupported actionType: " + req.ActionType},
			})
		}
	}

	c.JSON(http.StatusOK, results)
}

// Workflow Rules Endpoints (Task 10: Process API)

// WorkflowRule represents a workflow rule from Salesforce Process Rules API
type WorkflowRule struct {
	ID           string           `json:"id"`
	Name         string           `json:"name"`
	SObject      string           `json:"sObject,omitempty"`
	Active       bool             `json:"active"`
	Actions      []WorkflowAction `json:"actions,omitempty"`
	Criteria     string           `json:"criteria,omitempty"`
	TriggerTypes []string         `json:"triggerTypes,omitempty"`
	URL          string           `json:"url,omitempty"`
}

// WorkflowAction represents an action associated with a workflow rule
type WorkflowAction struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Type string `json:"type"`
	URL  string `json:"url,omitempty"`
}

// GetWorkflowRules handles GET /services/data/v63.0/process/rules
// Returns all workflow rules organized by sObject
func (h *ApprovalHandler) GetWorkflowRules(c *gin.Context) {
	MustGetUser(c)

	c.JSON(http.StatusOK, gin.H{
		"rules": map[string][]WorkflowRule{},
	})
}

// GetPendingApprovals returns the current user's pending approval queue using
// backend-internal reads instead of REST SOQL joins. This avoids relying on
// ProcessInstance queries that are not consistently visible through the public
// record-query surface.
func (h *ApprovalHandler) GetPendingApprovals(c *gin.Context) {
	user := MustGetUser(c)
	items, err := h.svc.ListPendingApprovals(c.Request.Context(), user)
	if err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}
	c.JSON(http.StatusOK, items)
}

// GetLatestProcessInstance returns the latest approval instance summary for the
// target record. The record detail UI uses this to build approval status without
// depending on public ProcessInstance queries.
func (h *ApprovalHandler) GetLatestProcessInstance(c *gin.Context) {
	user := MustGetUser(c)
	recordID := c.Param("recordId")
	if recordID == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "recordId is required"))
		return
	}
	summary, err := h.svc.GetLatestProcessInstance(c.Request.Context(), recordID, user)
	if err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}
	c.JSON(http.StatusOK, summary)
}

// GetWorkflowRulesForObject handles GET /services/data/v63.0/process/rules/:sObject
// Returns workflow rules for a specific sObject
func (h *ApprovalHandler) GetWorkflowRulesForObject(c *gin.Context) {
	sObject := c.Param("sObject")
	MustGetUser(c)

	// Validate sObject parameter
	if sObject == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "Missing sObject parameter"))
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"rules": []WorkflowRule{},
	})
}

// Private helpers

// buildProcessResult constructs a Salesforce-compliant ProcessResult from the service layer's
// work item SObject. Gracefully handles nil work items (stub service returns nil).
func buildProcessResult(workItem models.SObject, entityID, status string, user *models.UserSession) ProcessResult {
	result := ProcessResult{
		ActorIds:       []string{},
		EntityId:       entityID,
		Errors:         []string{},
		InstanceStatus: status,
		NewWorkitemIds: []string{},
		Success:        true,
	}

	if user != nil {
		result.ActorIds = []string{user.ID}
	}

	if workItem != nil {
		hasActorIDs := false
		if actorIDs, ok := workItem["ActorIds"].([]string); ok {
			hasActorIDs = true
			result.ActorIds = actorIDs
		}
		if actorIDsAny, ok := workItem["ActorIds"].([]interface{}); ok {
			hasActorIDs = true
			actorIDs := make([]string, 0, len(actorIDsAny))
			for _, actorID := range actorIDsAny {
				if raw, ok := actorID.(string); ok && raw != "" {
					actorIDs = append(actorIDs, raw)
				}
			}
			result.ActorIds = actorIDs
		}
		if !hasActorIDs && user != nil {
			result.ActorIds = []string{user.ID}
		}

		hasWorkItemIDs := false
		if workItemIDs, ok := workItem["NewWorkitemIds"].([]string); ok {
			hasWorkItemIDs = true
			result.NewWorkitemIds = workItemIDs
		}
		if workItemIDsAny, ok := workItem["NewWorkitemIds"].([]interface{}); ok {
			hasWorkItemIDs = true
			workItemIDs := make([]string, 0, len(workItemIDsAny))
			for _, workItemID := range workItemIDsAny {
				if raw, ok := workItemID.(string); ok && raw != "" {
					workItemIDs = append(workItemIDs, raw)
				}
			}
			result.NewWorkitemIds = workItemIDs
		}
		if id, ok := workItem[constants.FieldID].(string); ok {
			if !hasWorkItemIDs && len(result.NewWorkitemIds) == 0 {
				result.NewWorkitemIds = []string{id}
			}
		}
		if instanceID, ok := workItem["ProcessInstanceId"].(string); ok {
			result.InstanceId = instanceID
		}
		if eid, ok := workItem["TargetObjectId"].(string); ok {
			result.EntityId = eid
		}
	}

	return result
}

// handleApprovalErrorInBatch appends a failed ProcessResult for batch processing
// (instead of returning HTTP error, for bulk-capable approval requests).
func handleApprovalErrorInBatch(err error, entityId string, results *[]ProcessResult) {
	*results = append(*results, ProcessResult{
		Success:        false,
		EntityId:       entityId,
		InstanceStatus: "Error",
		ActorIds:       []string{},
		NewWorkitemIds: []string{},
		Errors:         []string{err.Error()},
	})
}

// CloneApprovalProcess handles POST /services/data/v63.0/process/approvals/clone
// Clones an existing approval process with all its steps and actions
func (h *ApprovalHandler) CloneApprovalProcess(c *gin.Context) {
	user := MustGetUser(c)

	// Parse the clone request body
	var req struct {
		ProcessDefinitionId string `json:"processDefinitionId"`
		NewDeveloperName    string `json:"newDeveloperName"`
		NewLabel            string `json:"newLabel"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeJSONParserError, "Invalid clone request: "+err.Error()))
		return
	}

	if req.ProcessDefinitionId == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "ProcessDefinitionId is required"))
		return
	}

	if req.NewDeveloperName == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "NewDeveloperName is required"))
		return
	}

	if req.NewLabel == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "NewLabel is required"))
		return
	}

	ctx := c.Request.Context()
	createdProcess, err := h.svc.Clone(ctx, req.ProcessDefinitionId, req.NewDeveloperName, req.NewLabel, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	// Return the created process definition
	c.JSON(http.StatusOK, createdProcess)
}

// GetProjectedChain handles GET /services/data/v63.0/process/projections
// Pre-evaluates each ProcessNode's entry criteria for the supplied record so
// the UI can label conditional steps Skipped/Not-Required immediately on
// submission, instead of showing every defined step as Upcoming until runtime
// transitions reach it. Backs issue #1946.
//
// Query params:
//   - processDefId   (required) — ProcessDefinition.Id
//   - recordId       (required) — target record id
//   - objectApiName  (optional) — defaults to ProcessDefinition.TableEnumOrId
func (h *ApprovalHandler) GetProjectedChain(c *gin.Context) {
	user := MustGetUser(c)

	processDefID := c.Query("processDefId")
	recordID := c.Query("recordId")
	objectAPIName := c.Query("objectApiName")

	if processDefID == "" || recordID == "" {
		c.JSON(http.StatusBadRequest, apierr.SFErrorResponse(apierr.SFCodeInvalidRequest, "processDefId and recordId are required"))
		return
	}

	projections, err := h.svc.ProjectChain(c.Request.Context(), processDefID, objectAPIName, recordID, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"projections": projections})
}

// RegisterReadRoutes registers approval routes that do not mutate metadata.
func (h *ApprovalHandler) RegisterReadRoutes(group *gin.RouterGroup) {
	// Standard Salesforce Endpoint: POST /services/data/v63.0/process/approvals
	group.POST("/approvals", h.HandleProcessApprovals)
	group.GET("/approvals/pending", h.GetPendingApprovals)
	group.GET("/approvals/records/:recordId/instance", h.GetLatestProcessInstance)

	// Task 10: Workflow Rules Endpoints
	group.GET("/rules", h.GetWorkflowRules)
	group.GET("/rules/:sObject", h.GetWorkflowRulesForObject)

	// Issue #1946: per-step entry-criteria projection so the chain UI can
	// distinguish required vs not-required approvers on submission.
	group.GET("/projections", h.GetProjectedChain)
}

// RegisterWriteRoutes registers approval routes that mutate approval metadata.
func (h *ApprovalHandler) RegisterWriteRoutes(group *gin.RouterGroup) {
	group.POST("/approvals/clone", h.CloneApprovalProcess)
}
