package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/mail"
	"regexp"
	"strings"

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

const (
	officialAppPacksRootEnvVar = "NEXUSCRM_OFFICIAL_APP_PACKS_ROOT"

	defaultDeveloperName       = "Unnamed"
	developerNameNumericPrefix = "X"
	developerNameMaxLength     = 80
	componentKindStandardFlow  = componentKind("standard_flow")
	componentKindApproval      = componentKind("approval_process")
)

var (
	nonAlphanumericOrUnderscore = regexp.MustCompile(`[^a-zA-Z0-9_]`)
	consecutiveUnderscores      = regexp.MustCompile(`_+`)

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

func validateCanonicalSendEmailAddress(address string) (string, bool) {
	address = strings.TrimSpace(address)
	if address == "" {
		return "", false
	}
	parsed, err := mail.ParseAddress(address)
	if err != nil || parsed == nil || parsed.Name != "" || parsed.Address != address {
		return "", false
	}
	return address, true
}

func sanitizeDeveloperName(name string) string {
	if name == "" {
		return ""
	}
	clean := strings.ReplaceAll(name, " ", "_")
	clean = nonAlphanumericOrUnderscore.ReplaceAllString(clean, "")
	clean = consecutiveUnderscores.ReplaceAllString(clean, "_")
	clean = strings.Trim(clean, "_")
	if len(clean) > 0 && clean[0] >= '0' && clean[0] <= '9' {
		clean = developerNameNumericPrefix + clean
	}
	if clean == "" {
		clean = defaultDeveloperName
	}
	if len(clean) > developerNameMaxLength {
		clean = strings.TrimSuffix(clean[:developerNameMaxLength], "_")
	}
	return clean
}

type triggerProjectionKind int

const (
	triggerProjectionKindUnknown triggerProjectionKind = iota
	triggerProjectionKindRecord
	triggerProjectionKindExplicitNonRecord
)

type triggerProjection struct {
	Kind          triggerProjectionKind
	TriggerType   string
	ObjectOrEvent string
}

func resolveFlowTriggerProjection(def *models.FlowDefinition, version *models.FlowVersion) (triggerProjection, error) {
	if version == nil {
		return triggerProjection{Kind: triggerProjectionKindUnknown}, nil
	}

	metadata, err := metadataForFlowTriggerProjection(version)
	if err != nil {
		return triggerProjection{Kind: triggerProjectionKindUnknown}, err
	}

	processType := firstNonEmpty(version.ProcessType, flowDefinitionProcessType(def))
	if metadata.Start != nil {
		projection, err := resolveFlowStartProjection(metadata.Start)
		if err != nil {
			return triggerProjection{Kind: triggerProjectionKindUnknown}, err
		}
		if projection.Kind != triggerProjectionKindUnknown {
			return projection, nil
		}
		if strings.EqualFold(processType, "Workflow") {
			return resolveWorkflowProjection(metadata.Variables)
		}
		if isExplicitNonRecordProcessType(processType) {
			return triggerProjection{Kind: triggerProjectionKindExplicitNonRecord}, nil
		}
		return triggerProjection{Kind: triggerProjectionKindUnknown}, nil
	}

	if strings.EqualFold(processType, "Workflow") {
		return resolveWorkflowProjection(metadata.Variables)
	}
	if isExplicitNonRecordProcessType(processType) {
		return triggerProjection{Kind: triggerProjectionKindExplicitNonRecord}, nil
	}
	return triggerProjection{Kind: triggerProjectionKindUnknown}, nil
}

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

func resolveFlowStartProjection(start *models.FlowStart) (triggerProjection, error) {
	triggerType := strings.TrimSpace(start.TriggerType)
	objectOrEvent := strings.TrimSpace(start.Object)
	if triggerType == "" && objectOrEvent == "" {
		return triggerProjection{Kind: triggerProjectionKindUnknown}, nil
	}
	if triggerType == "" {
		return triggerProjection{Kind: triggerProjectionKindUnknown}, fmt.Errorf("%w: start.triggerType is required when start.object is set", errMalformedFlowTriggerProjection)
	}
	if canonical, ok := canonicalRecordTriggerType(triggerType); ok {
		if objectOrEvent == "" {
			return triggerProjection{Kind: triggerProjectionKindUnknown}, fmt.Errorf("%w: %s start.object is required", errMalformedFlowTriggerProjection, canonical)
		}
		return triggerProjection{
			Kind:          triggerProjectionKindRecord,
			TriggerType:   canonical,
			ObjectOrEvent: objectOrEvent,
		}, nil
	}
	if canonical, ok := canonicalNonRecordTriggerType(triggerType); ok {
		return triggerProjection{
			Kind:          triggerProjectionKindExplicitNonRecord,
			TriggerType:   canonical,
			ObjectOrEvent: objectOrEvent,
		}, nil
	}
	if objectOrEvent != "" {
		return triggerProjection{Kind: triggerProjectionKindUnknown}, fmt.Errorf("%w: unsupported start.triggerType %q for start.object %q", errMalformedFlowTriggerProjection, triggerType, objectOrEvent)
	}
	return triggerProjection{Kind: triggerProjectionKindUnknown}, nil
}

func metadataForFlowTriggerProjection(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", errMalformedFlowTriggerProjection, 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", errMalformedFlowTriggerProjection, 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 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", errMalformedFlowTriggerProjection)
			}
			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 ""
}

type componentKind string

type packManifest struct {
	ManifestFormatVersion string                     `json:"manifestFormatVersion"`
	Name                  string                     `json:"name"`
	DisplayName           string                     `json:"displayName"`
	Category              string                     `json:"category"`
	Version               string                     `json:"version"`
	PlatformCompatibility packPlatformCompatibility  `json:"platformCompatibility"`
	Provenance            *packProvenance            `json:"provenance,omitempty"`
	Dependencies          []packDependency           `json:"dependencies"`
	HostRequirements      []hostPrimitiveRequirement `json:"hostRequirements,omitempty"`
	Components            []manifestComponent        `json:"components"`
	Extensions            json.RawMessage            `json:"extensions,omitempty"`
}

type packPlatformCompatibility struct {
	Min          string `json:"min"`
	MaxExclusive string `json:"maxExclusive"`
}

type packProvenance struct {
	SourceSystem   string `json:"sourceSystem,omitempty"`
	SourceExportID string `json:"sourceExportId,omitempty"`
	GeneratedAt    string `json:"generatedAt,omitempty"`
}

type packDependency struct {
	Name              string `json:"name"`
	VersionConstraint string `json:"versionConstraint,omitempty"`
}

type hostPrimitiveRequirement struct {
	Kind    string `json:"kind"`
	Name    string `json:"name"`
	Version string `json:"version,omitempty"`
}

type manifestComponent struct {
	Kind          componentKind        `json:"kind"`
	BusinessKey   string               `json:"businessKey"`
	Manageability string               `json:"manageability"`
	Path          string               `json:"path,omitempty"`
	Payload       json.RawMessage      `json:"payload,omitempty"`
	DependsOn     []componentReference `json:"dependsOn,omitempty"`
}

type componentReference struct {
	Kind        componentKind `json:"kind"`
	BusinessKey string        `json:"businessKey"`
	Required    bool          `json:"required,omitempty"`
}
