package triggerprojection

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/nexuscrm/shared/pkg/models"
)

var ErrMalformed = errors.New("flow trigger projection metadata is malformed")

type Kind int

const (
	KindUnknown Kind = iota
	KindRecord
	KindExplicitNonRecord
)

type Projection struct {
	Kind          Kind
	TriggerType   string
	ObjectOrEvent string
}

func Resolve(def *models.FlowDefinition, version *models.FlowVersion) (Projection, error) {
	if version == nil {
		return Projection{Kind: KindUnknown}, nil
	}

	metadata, err := metadataForProjection(version)
	if err != nil {
		return Projection{Kind: KindUnknown}, err
	}

	processType := firstNonEmpty(version.ProcessType, flowDefinitionProcessType(def))
	if metadata.Start != nil {
		projection, err := resolveStart(metadata.Start)
		if err != nil {
			return Projection{Kind: KindUnknown}, err
		}
		if projection.Kind != KindUnknown {
			return projection, nil
		}
		if strings.EqualFold(processType, "Workflow") {
			return resolveWorkflowProjection(metadata.Variables)
		}
		if isExplicitNonRecordProcessType(processType) {
			return Projection{Kind: KindExplicitNonRecord}, nil
		}
		return Projection{Kind: KindUnknown}, nil
	}

	if strings.EqualFold(processType, "Workflow") {
		return resolveWorkflowProjection(metadata.Variables)
	}

	if isExplicitNonRecordProcessType(processType) {
		return Projection{Kind: KindExplicitNonRecord}, nil
	}

	return Projection{Kind: KindUnknown}, nil
}

func resolveWorkflowProjection(variables []models.FlowVariable) (Projection, error) {
	object, err := workflowTriggerObject(variables)
	if err != nil {
		return Projection{Kind: KindUnknown}, err
	}
	if object == "" {
		return Projection{Kind: KindUnknown}, nil
	}
	return Projection{
		Kind:          KindRecord,
		TriggerType:   "RecordAfterSave",
		ObjectOrEvent: object,
	}, nil
}

func resolveStart(start *models.FlowStart) (Projection, error) {
	triggerType := strings.TrimSpace(start.TriggerType)
	objectOrEvent := strings.TrimSpace(start.Object)
	if triggerType == "" && objectOrEvent == "" {
		return Projection{Kind: KindUnknown}, nil
	}
	if triggerType == "" {
		return Projection{Kind: KindUnknown}, fmt.Errorf("%w: start.triggerType is required when start.object is set", ErrMalformed)
	}
	if canonical, ok := CanonicalRecordTriggerType(triggerType); ok {
		if objectOrEvent == "" {
			return Projection{Kind: KindUnknown}, fmt.Errorf("%w: %s start.object is required", ErrMalformed, canonical)
		}
		return Projection{
			Kind:          KindRecord,
			TriggerType:   canonical,
			ObjectOrEvent: objectOrEvent,
		}, nil
	}
	if canonical, ok := canonicalNonRecordTriggerType(triggerType); ok {
		return Projection{
			Kind:          KindExplicitNonRecord,
			TriggerType:   canonical,
			ObjectOrEvent: objectOrEvent,
		}, nil
	}
	if objectOrEvent != "" {
		return Projection{Kind: KindUnknown}, fmt.Errorf("%w: unsupported start.triggerType %q for start.object %q", ErrMalformed, triggerType, objectOrEvent)
	}
	return Projection{Kind: KindUnknown}, nil
}

func metadataForProjection(version *models.FlowVersion) (models.FlowMetadata, error) {
	raw := bytes.TrimSpace(version.MetadataRaw)
	if len(raw) == 0 || bytes.Equal(raw, []byte("{}")) {
		return version.Metadata, nil
	}

	var wire struct {
		Start     *json.RawMessage      `json:"start"`
		Variables []models.FlowVariable `json:"variables"`
	}
	if err := json.Unmarshal(raw, &wire); err != nil {
		return models.FlowMetadata{}, fmt.Errorf("%w: parse flow metadata: %v", ErrMalformed, err)
	}

	metadata := models.FlowMetadata{
		Variables: wire.Variables,
	}
	if wire.Start == nil {
		return metadata, nil
	}
	startRaw := bytes.TrimSpace(*wire.Start)
	if len(startRaw) == 0 || bytes.Equal(startRaw, []byte("null")) {
		return metadata, nil
	}
	var start models.FlowStart
	if err := json.Unmarshal(startRaw, &start); err != nil {
		return models.FlowMetadata{}, fmt.Errorf("%w: parse flow start metadata: %v", ErrMalformed, err)
	}
	metadata.Start = &start
	return metadata, nil
}

func CanonicalRecordTriggerType(value string) (string, bool) {
	trimmed := strings.TrimSpace(value)
	switch {
	case strings.EqualFold(trimmed, "RecordBeforeSave"):
		return "RecordBeforeSave", true
	case strings.EqualFold(trimmed, "RecordAfterSave"):
		return "RecordAfterSave", true
	case strings.EqualFold(trimmed, "RecordBeforeDelete"):
		return "RecordBeforeDelete", true
	case strings.EqualFold(trimmed, "RecordAfterDelete"):
		return "RecordAfterDelete", true
	case strings.EqualFold(trimmed, "onAllChanges"):
		return "RecordAfterSave", true
	}
	return "", false
}

func canonicalNonRecordTriggerType(value string) (string, bool) {
	switch {
	case strings.EqualFold(value, "Scheduled"):
		return "Scheduled", true
	case strings.EqualFold(value, "PlatformEvent"):
		return "PlatformEvent", true
	}
	return "", false
}

func IsRecordTriggerType(value string) bool {
	_, ok := CanonicalRecordTriggerType(value)
	return ok
}

func isExplicitNonRecordProcessType(value string) bool {
	switch {
	case strings.EqualFold(value, "AutoLaunchedFlow"),
		strings.EqualFold(value, "ScheduledFlow"),
		strings.EqualFold(value, "CustomEvent"),
		strings.EqualFold(value, "InvocableProcess"),
		strings.EqualFold(value, "Flow"):
		return true
	}
	return false
}

func workflowTriggerObject(variables []models.FlowVariable) (string, error) {
	for _, v := range variables {
		if strings.EqualFold(v.Name, "myVariable_current") &&
			strings.EqualFold(v.DataType, "SObject") {
			objectType := strings.TrimSpace(v.ObjectType)
			if objectType == "" {
				return "", fmt.Errorf("%w: Workflow myVariable_current.objectType is required", ErrMalformed)
			}
			return objectType, nil
		}
	}
	return "", nil
}

func flowDefinitionProcessType(def *models.FlowDefinition) string {
	if def == nil {
		return ""
	}
	return def.ProcessType
}

func firstNonEmpty(candidates ...string) string {
	for _, candidate := range candidates {
		if strings.TrimSpace(candidate) != "" {
			return candidate
		}
	}
	return ""
}
