package rest

import (
	"context"
	"database/sql"
	"fmt"
	"net/http"
	"strings"

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

// Actions API - Invocable Flows

// ListInvocableFlows handles GET /services/data/v63.0/actions/custom/flow
// Returns list of flows that can be invoked via the Actions API
func (h *FlowHandler) ListInvocableFlows(c *gin.Context) {
	MustGetUser(c)

	ctx := c.Request.Context()
	protectedFlows, err := h.protectedFlowNames(ctx)
	if err != nil {
		RespondAppError(c, apierr.Internal(err))
		return
	}

	// Get all active flow definitions from metadata
	flowDefs := h.metadata.GetFlowDefinitions(ctx)

	// Convert to InvocableFlow format
	invocableFlows := make([]models.InvocableFlow, 0)
	for _, def := range flowDefs {
		if _, protected := protectedFlows[protectedFlowKey(def.DeveloperName)]; protected {
			continue
		}
		if def.ActiveVersionId != "" { // Has an active version
			invocableFlows = append(invocableFlows, models.InvocableFlow{
				Label:       def.MasterLabel,
				Name:        def.DeveloperName,
				Type:        "FLOW",
				Url:         "/services/data/v63.0/actions/custom/flow/" + def.DeveloperName,
				Description: def.Description,
			})
		}
	}

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

// GetFlowSchema handles GET /services/data/v63.0/actions/custom/flow/:name
// Returns the input/output schema for a specific flow
func (h *FlowHandler) GetFlowSchema(c *gin.Context) {
	flowName := c.Param("name")
	MustGetUser(c)

	ctx := c.Request.Context()
	if h.rejectProtectedFlow(c, ctx, flowName) {
		return
	}

	// Find the flow definition by name
	flowDef := h.metadata.GetFlowDefinitionByName(ctx, flowName)
	if flowDef == nil {
		RespondAppError(c, apierr.NotFound("Flow"))
		return
	}

	// Build schema response
	response := models.InvocableFlow{
		Label:       flowDef.MasterLabel,
		Name:        flowDef.DeveloperName,
		Type:        "FLOW",
		Url:         "/services/data/v63.0/actions/custom/flow/" + flowDef.DeveloperName,
		Description: flowDef.Description,
		Inputs:      []models.FlowInputOutput{},
		Outputs:     []models.FlowInputOutput{},
	}

	// Get active flow version to extract input/output variables
	if flowDef.ActiveVersionId != "" {
		flowVersion := h.metadata.GetFlowVersionById(ctx, flowDef.ActiveVersionId)
		if flowVersion != nil {
			for _, variable := range flowVersion.Metadata.Variables {
				if variable.IsInput {
					response.Inputs = append(response.Inputs, models.FlowInputOutput{
						Name:     variable.Name,
						Label:    variable.Name, // Use name as label if not available
						DataType: variable.DataType,
						Required: false, // Could be enhanced with additional metadata
					})
				}
				if variable.IsOutput {
					response.Outputs = append(response.Outputs, models.FlowInputOutput{
						Name:     variable.Name,
						Label:    variable.Name,
						DataType: variable.DataType,
					})
				}
			}
		}
	}

	c.JSON(http.StatusOK, response)
}

// ExecuteFlow handles POST /services/data/v63.0/actions/custom/flow/:name
// Executes a flow with the provided inputs
func (h *FlowHandler) ExecuteFlow(c *gin.Context) {
	flowName := c.Param("name")
	user := MustGetUser(c)
	ctx := c.Request.Context()
	if h.rejectProtectedFlow(c, ctx, flowName) {
		return
	}

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

	// Execute for each input set
	results := make([]models.FlowExecutionResult, 0, len(req.Inputs))
	for _, inputs := range req.Inputs {
		result, err := h.executor.ExecuteFlow(ctx, flowName, inputs, user)
		if err != nil {
			results = append(results, models.FlowExecutionResult{
				ActionName: flowName,
				IsSuccess:  false,
				Errors: []models.FlowExecutionError{{
					StatusCode: "FLOW_EXECUTION_ERROR",
					Message:    err.Error(),
				}},
			})
		} else if result != nil {
			results = append(results, *result)
		} else {
			// Defensive: result is nil but no error (shouldn't happen, but handle gracefully)
			results = append(results, models.FlowExecutionResult{
				ActionName: flowName,
				IsSuccess:  false,
				Errors: []models.FlowExecutionError{{
					StatusCode: "UNKNOWN_ERROR",
					Message:    "Flow execution returned no result",
				}},
			})
		}
	}

	c.JSON(http.StatusOK, results)
}

func (h *FlowHandler) rejectProtectedFlow(c *gin.Context, ctx context.Context, flowName string) bool {
	protected, err := h.isProtectedFlow(ctx, flowName)
	if err != nil {
		RespondAppError(c, apierr.Internal(err))
		return true
	}
	if !protected {
		return false
	}
	RespondAppError(c, apierr.NotFound("Flow"))
	return true
}

func (h *FlowHandler) isProtectedFlow(ctx context.Context, flowName string) (bool, error) {
	if strings.TrimSpace(flowName) == "" {
		return false, nil
	}
	names, err := h.protectedFlowNames(ctx)
	if err != nil {
		return false, err
	}
	_, ok := names[protectedFlowKey(flowName)]
	return ok, nil
}

func protectedFlowKey(flowName string) string {
	return strings.ToLower(strings.TrimSpace(flowName))
}

func (h *FlowHandler) protectedFlowNames(ctx context.Context) (map[string]struct{}, error) {
	if h.db == nil {
		return nil, fmt.Errorf("standard flow registry database is not configured")
	}

	names := make(map[string]struct{})
	if err := h.addStandardActionFlowNames(ctx, names); err != nil {
		return nil, err
	}
	if err := h.addPackOwnedStandardFlowNames(ctx, names); err != nil {
		return nil, err
	}
	return names, nil
}

func (h *FlowHandler) addStandardActionFlowNames(ctx context.Context, names map[string]struct{}) error {
	query := fmt.Sprintf(
		`SELECT %s
		   FROM %s
		  WHERE %s IS NOT NULL AND %s <> ''
		    AND (%s = 0 OR %s IS NULL)
		    AND (%s = 0 OR %s IS NULL)`,
		constants.FieldSysStandardAction_FlowDeveloperName,
		constants.TableStandardAction,
		constants.FieldSysStandardAction_FlowDeveloperName,
		constants.FieldSysStandardAction_FlowDeveloperName,
		constants.FieldSysStandardAction_IsDeleted,
		constants.FieldSysStandardAction_IsDeleted,
		constants.FieldSysStandardAction_IsCustomized,
		constants.FieldSysStandardAction_IsCustomized,
	)
	rows, err := h.db.QueryContext(ctx, query)
	if err != nil {
		return fmt.Errorf("load standard action flow names: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var flowName sql.NullString
		if err := rows.Scan(&flowName); err != nil {
			return fmt.Errorf("scan standard action flow name: %w", err)
		}
		if flowName.Valid {
			if key := protectedFlowKey(flowName.String); key != "" {
				names[key] = struct{}{}
			}
		}
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("iterate standard action flow names: %w", err)
	}
	return nil
}

func (h *FlowHandler) addPackOwnedStandardFlowNames(ctx context.Context, names map[string]struct{}) error {
	query := fmt.Sprintf(
		`SELECT fd.%s
		   FROM %s fd
		   JOIN %s f ON f.%s = fd.%s AND f.%s = 0
		  WHERE fd.%s IS NOT NULL AND fd.%s <> ''
		    AND fd.%s = 0
		    AND fd.%s = 0
		    AND f.%s = ?`,
		constants.FieldFlowDefinition_DeveloperName,
		constants.TableFlowDefinition,
		constants.TableFlow,
		constants.FieldFlow_ID,
		constants.FieldFlowDefinition_ActiveVersionID,
		constants.FieldFlow_IsDeleted,
		constants.FieldFlowDefinition_DeveloperName,
		constants.FieldFlowDefinition_DeveloperName,
		constants.FieldFlowDefinition_IsDeleted,
		constants.FieldFlowDefinition_IsCustomized,
		constants.FieldFlow_Source,
	)
	rows, err := h.db.QueryContext(ctx, query, bootstrap.FlowSourceStandard)
	if err != nil {
		return fmt.Errorf("load pack-owned standard flow names: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var flowName sql.NullString
		if err := rows.Scan(&flowName); err != nil {
			return fmt.Errorf("scan pack-owned standard flow name: %w", err)
		}
		if flowName.Valid {
			if key := protectedFlowKey(flowName.String); key != "" {
				names[key] = struct{}{}
			}
		}
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("iterate pack-owned standard flow names: %w", err)
	}
	return nil
}
