package automation

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"strconv"
	"strings"

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

// ApprovalProcessXML mirrors the subset of approval-process Metadata API XML
// that NexusCRM currently consumes for approval setup and execution.
type ApprovalProcessXML struct {
	XMLName                  xml.Name                  `xml:"ApprovalProcess"`
	Label                    string                    `xml:"label"`
	Active                   bool                      `xml:"active"`
	Description              string                    `xml:"description"`
	AllowedSubmitters        []AllowedSubmitterXML     `xml:"allowedSubmitters"`
	NextAutomatedApprover    *NextAutomatedApproverXML `xml:"nextAutomatedApprover"`
	Steps                    []ApprovalStepXML         `xml:"approvalStep"`
	ApprovalPageFields       []string                  `xml:"approvalPageFields>field"`
	EnableMobileDeviceAccess *bool                     `xml:"enableMobileDeviceAccess"`
	ProcessOrder             int                       `xml:"processOrder"`
}

type NextAutomatedApproverXML struct {
	UserHierarchyField            string `xml:"userHierarchyField"`
	UseApproverFieldOfRecordOwner *bool  `xml:"useApproverFieldOfRecordOwner"`
}

type ApprovalStepXML struct {
	Name             string               `xml:"name"`
	Label            string               `xml:"label"`
	Description      string               `xml:"description"`
	AllowDelegate    *bool                `xml:"allowDelegate"`
	AssignedApprover *AssignedApproverXML `xml:"assignedApprover"`
	RejectBehavior   *RejectBehaviorXML   `xml:"rejectBehavior"`
	StepCriteria     *CriteriaXML         `xml:"stepCriteria"`
	EntryCriteria    *CriteriaXML         `xml:"entryCriteria"`
	IfCriteriaNotMet string               `xml:"ifCriteriaNotMet"`
}

type AssignedApproverXML struct {
	Approvers             []ApproverXML `xml:"approver"`
	WhenMultipleApprovers string        `xml:"whenMultipleApprovers"`
}

type ApproverXML struct {
	Name string `xml:"name"`
	Type string `xml:"type"`
}

type RejectBehaviorXML struct {
	Type string `xml:"type"`
}

type CriteriaXML struct {
	Formula       string            `xml:"formula"`
	BooleanFilter string            `xml:"booleanFilter"`
	CriteriaItems []CriteriaItemXML `xml:"criteriaItems"`
}

type CriteriaItemXML struct {
	Field     string `xml:"field"`
	Operation string `xml:"operation"`
	Value     string `xml:"value"`
}

type NormalizedApprovalProcess struct {
	HasExplicitAllowedSubmitters bool
	AllowedSubmitters            []NormalizedAllowedSubmitter
	NextAutomatedApprover        *NormalizedNextAutomatedApprover
	Steps                        []NormalizedApprovalStep
	ApprovalPageFields           []string
	EnableMobileDeviceAccess     bool
	ProcessOrder                 int
}

type NormalizedNextAutomatedApprover struct {
	UserHierarchyField            string
	UseApproverFieldOfRecordOwner bool
}

type NormalizedApprovalStep struct {
	Name                  string
	Label                 string
	Description           string
	AllowDelegate         bool
	Approvers             []NormalizedApprovalApprover
	WhenMultipleApprovers string
	RejectBehavior        string
	StepCriteria          *NormalizedCriteria
	// IfCriteriaNotMet controls advancement when StepCriteria does not match the
	// target record. Normalized values: "GotoNextStep" (skip this step),
	// "ApproveRecord" (finalize Approved), or "RejectRecord" (finalize Rejected).
	// Empty means the field was omitted and defaults to "ApproveRecord" per SF.
	IfCriteriaNotMet string
}

type NormalizedApprovalApprover struct {
	Name string
	Type string
}

type AllowedSubmitterXML struct {
	Submitter string `xml:"submitter"`
	Type      string `xml:"type"`
}

type NormalizedAllowedSubmitter struct {
	Submitter string
	Type      string
}

type NormalizedCriteria struct {
	Formula       string
	BooleanFilter string
	CriteriaItems []NormalizedCriteriaItem
}

type NormalizedCriteriaItem struct {
	Field     string
	Operation string
	Value     string
}

type approvalProcessJSON struct {
	AllowedSubmittersRaw     json.RawMessage           `json:"allowedSubmitters"`
	NextAutomatedApprover    nextAutomatedApproverJSON `json:"nextAutomatedApprover"`
	Steps                    approvalStepsJSON         `json:"approvalStep"`
	ApprovalPageFieldsRaw    interface{}               `json:"approvalPageFields,omitempty"`
	EnableMobileDeviceAccess stringBool                `json:"enableMobileDeviceAccess,omitempty"`
	ProcessOrderString       string                    `json:"processOrder,omitempty"`
}

type allowedSubmitterListJSON []allowedSubmitterJSON

func (s *allowedSubmitterListJSON) UnmarshalJSON(data []byte) error {
	return decodeOneOrMany(data, (*[]allowedSubmitterJSON)(s))
}

type allowedSubmitterJSON struct {
	Submitter string `json:"submitter"`
	Type      string `json:"type"`
}

type approvalStepsJSON []approvalStepJSON

func (s *approvalStepsJSON) UnmarshalJSON(data []byte) error {
	return decodeOneOrMany(data, (*[]approvalStepJSON)(s))
}

type approvalStepJSON struct {
	Name             string               `json:"-"`
	Label            string               `json:"-"`
	Description      string               `json:"-"`
	AllowDelegate    stringBool           `json:"-"`
	AssignedApprover assignedApproverJSON `json:"-"`
	RejectBehavior   rejectBehaviorJSON   `json:"-"`
	// StepCriteria is populated from whichever of stepCriteria / entryCriteria
	// is present in the JSON payload. Salesforce-exported metadata uses
	// "entryCriteria" at the step level; NexusCRM-authored XML uses
	// "stepCriteria".
	StepCriteria     *criteriaJSON `json:"-"`
	IfCriteriaNotMet string        `json:"-"`
}

func (s *approvalStepJSON) UnmarshalJSON(data []byte) error {
	var raw struct {
		Name             string               `json:"name"`
		Label            string               `json:"label"`
		Description      string               `json:"description"`
		AllowDelegate    stringBool           `json:"allowDelegate"`
		AssignedApprover assignedApproverJSON `json:"assignedApprover"`
		RejectBehavior   rejectBehaviorJSON   `json:"rejectBehavior"`
		StepCriteria     *criteriaJSON        `json:"stepCriteria"`
		EntryCriteria    *criteriaJSON        `json:"entryCriteria"`
		IfCriteriaNotMet string               `json:"ifCriteriaNotMet"`
	}
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}
	s.Name = raw.Name
	s.Label = raw.Label
	s.Description = raw.Description
	s.AllowDelegate = raw.AllowDelegate
	s.AssignedApprover = raw.AssignedApprover
	s.RejectBehavior = raw.RejectBehavior
	s.IfCriteriaNotMet = raw.IfCriteriaNotMet
	// Prefer entryCriteria (Salesforce-exported shape) over stepCriteria
	// (NexusCRM frontend shape) when both happen to be present.
	if raw.EntryCriteria != nil {
		s.StepCriteria = raw.EntryCriteria
	} else {
		s.StepCriteria = raw.StepCriteria
	}
	return nil
}

type assignedApproverJSON struct {
	Approvers             approverListJSON `json:"approver"`
	WhenMultipleApprovers string           `json:"whenMultipleApprovers"`
}

type approverListJSON []approverJSON

func (a *approverListJSON) UnmarshalJSON(data []byte) error {
	return decodeOneOrMany(data, (*[]approverJSON)(a))
}

type approverJSON struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

type nextAutomatedApproverJSON struct {
	UserHierarchyField            string     `json:"userHierarchyField"`
	UseApproverFieldOfRecordOwner stringBool `json:"useApproverFieldOfRecordOwner"`
}

type rejectBehaviorJSON struct {
	Type string
}

func (r *rejectBehaviorJSON) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	if trimmed[0] == '"' {
		return json.Unmarshal(trimmed, &r.Type)
	}
	type alias struct {
		Type string `json:"type"`
	}
	var raw alias
	if err := json.Unmarshal(trimmed, &raw); err != nil {
		return err
	}
	r.Type = raw.Type
	return nil
}

type criteriaJSON struct {
	Formula       string            `json:"formula"`
	BooleanFilter string            `json:"booleanFilter"`
	CriteriaItems criteriaItemsJSON `json:"criteriaItems"`
}

type criteriaItemsJSON []criteriaItemJSON

func (c *criteriaItemsJSON) UnmarshalJSON(data []byte) error {
	return decodeOneOrMany(data, (*[]criteriaItemJSON)(c))
}

type nullableString struct {
	String string
	Valid  bool
}

func (ns *nullableString) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		ns.Valid = false
		ns.String = ""
		return nil
	}
	if trimmed[0] == '"' {
		var raw string
		if err := json.Unmarshal(trimmed, &raw); err != nil {
			return err
		}
		ns.String = raw
		ns.Valid = true
		return nil
	}
	ns.String = string(trimmed)
	ns.Valid = true
	return nil
}

type criteriaItemJSON struct {
	Field     string         `json:"field"`
	Operation string         `json:"operation"`
	Value     nullableString `json:"value"`
}

type stringBool bool

func (b *stringBool) UnmarshalJSON(data []byte) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		return nil
	}
	if trimmed[0] == '"' {
		var raw string
		if err := json.Unmarshal(trimmed, &raw); err != nil {
			return err
		}
		*b = stringBool(strings.EqualFold(raw, "true"))
		return nil
	}
	var raw bool
	if err := json.Unmarshal(trimmed, &raw); err != nil {
		return err
	}
	*b = stringBool(raw)
	return nil
}

func ParseApprovalProcessMetadata(raw string) (*NormalizedApprovalProcess, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil, nil
	}
	switch trimmed[0] {
	case '<':
		return parseApprovalProcessXML(trimmed)
	case '{':
		return parseApprovalProcessJSON(trimmed)
	default:
		return nil, fmt.Errorf("unsupported approval metadata format")
	}
}

func parseApprovalProcessXML(raw string) (*NormalizedApprovalProcess, error) {
	var metadata ApprovalProcessXML
	if err := xml.Unmarshal([]byte(raw), &metadata); err != nil {
		return nil, err
	}
	process := &NormalizedApprovalProcess{
		HasExplicitAllowedSubmitters: len(metadata.AllowedSubmitters) > 0,
		AllowedSubmitters:            normalizeXMLAllowedSubmitters(metadata.AllowedSubmitters),
		NextAutomatedApprover:        normalizeNextAutomatedApproverXML(metadata.NextAutomatedApprover),
		Steps:                        make([]NormalizedApprovalStep, 0, len(metadata.Steps)),
		ApprovalPageFields:           metadata.ApprovalPageFields,
		EnableMobileDeviceAccess:     metadata.EnableMobileDeviceAccess != nil && *metadata.EnableMobileDeviceAccess,
		ProcessOrder:                 metadata.ProcessOrder,
	}
	for _, step := range metadata.Steps {
		criteria := step.StepCriteria
		if criteria == nil {
			criteria = step.EntryCriteria
		}
		process.Steps = append(process.Steps, NormalizedApprovalStep{
			Name:                  strings.TrimSpace(step.Name),
			Label:                 strings.TrimSpace(step.Label),
			Description:           strings.TrimSpace(step.Description),
			AllowDelegate:         step.AllowDelegate != nil && *step.AllowDelegate,
			Approvers:             normalizeXMLApprovers(step.AssignedApprover),
			WhenMultipleApprovers: normalizeApprovalMode(step.AssignedApprover),
			RejectBehavior:        normalizeRejectBehaviorXML(step.RejectBehavior),
			StepCriteria:          normalizeCriteriaXML(criteria),
			IfCriteriaNotMet:      normalizeIfCriteriaNotMet(step.IfCriteriaNotMet),
		})
	}
	return process, nil
}

func parseApprovalProcessJSON(raw string) (*NormalizedApprovalProcess, error) {
	var metadata approvalProcessJSON
	if err := json.Unmarshal([]byte(raw), &metadata); err != nil {
		return nil, err
	}
	var allowedSubmitters allowedSubmitterListJSON
	hasExplicitAllowedSubmitters := false
	if trimmed := bytes.TrimSpace(metadata.AllowedSubmittersRaw); len(trimmed) > 0 && !bytes.Equal(trimmed, []byte("null")) {
		hasExplicitAllowedSubmitters = true
		if err := json.Unmarshal(metadata.AllowedSubmittersRaw, &allowedSubmitters); err != nil {
			return nil, err
		}
	}

	// Handle approvalPageFields which can be either a direct array or an object with "field" property
	approvalPageFields := []string{}
	if metadata.ApprovalPageFieldsRaw != nil {
		switch v := metadata.ApprovalPageFieldsRaw.(type) {
		case []interface{}:
			// Direct array format: ["field1", "field2"]
			for _, item := range v {
				if str, ok := item.(string); ok {
					approvalPageFields = append(approvalPageFields, str)
				}
			}
		case map[string]interface{}:
			// Object format with field property: {"field": ["field1", "field2"]}
			if fieldVal, ok := v["field"]; ok {
				if fieldArray, ok := fieldVal.([]interface{}); ok {
					for _, item := range fieldArray {
						if str, ok := item.(string); ok {
							approvalPageFields = append(approvalPageFields, str)
						}
					}
				}
			}
		}
	}

	// Convert ProcessOrder from string to int
	processOrder := 0
	if metadata.ProcessOrderString != "" {
		if val, err := strconv.Atoi(metadata.ProcessOrderString); err == nil {
			processOrder = val
		}
	}

	process := &NormalizedApprovalProcess{
		HasExplicitAllowedSubmitters: hasExplicitAllowedSubmitters,
		AllowedSubmitters:            normalizeJSONAllowedSubmitters(allowedSubmitters),
		NextAutomatedApprover:        normalizeNextAutomatedApproverJSON(metadata.NextAutomatedApprover),
		Steps:                        make([]NormalizedApprovalStep, 0, len(metadata.Steps)),
		ApprovalPageFields:           approvalPageFields,
		EnableMobileDeviceAccess:     bool(metadata.EnableMobileDeviceAccess),
		ProcessOrder:                 processOrder,
	}
	for _, step := range metadata.Steps {
		process.Steps = append(process.Steps, NormalizedApprovalStep{
			Name:                  strings.TrimSpace(step.Name),
			Label:                 strings.TrimSpace(step.Label),
			Description:           strings.TrimSpace(step.Description),
			AllowDelegate:         bool(step.AllowDelegate),
			Approvers:             normalizeJSONApprovers(step.AssignedApprover.Approvers),
			WhenMultipleApprovers: normalizeApprovalModeValue(step.AssignedApprover.WhenMultipleApprovers),
			RejectBehavior:        normalizeRejectBehaviorValue(step.RejectBehavior.Type),
			StepCriteria:          normalizeCriteriaJSON(step.StepCriteria),
			IfCriteriaNotMet:      normalizeIfCriteriaNotMet(step.IfCriteriaNotMet),
		})
	}
	return process, nil
}

// normalizeIfCriteriaNotMet canonicalises the valid Salesforce values.
// Any other input (including empty) passes through — callers should treat
// empty as the SF default, which is "ApproveRecord".
func normalizeIfCriteriaNotMet(raw string) string {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "gotonextstep":
		return "GotoNextStep"
	case "approverecord":
		return "ApproveRecord"
	case "rejectrecord":
		return "RejectRecord"
	default:
		return strings.TrimSpace(raw)
	}
}

func (p *NormalizedApprovalProcess) StepForNames(developerName, label string) *NormalizedApprovalStep {
	if p == nil {
		return nil
	}
	for idx := range p.Steps {
		step := &p.Steps[idx]
		if developerName != "" && strings.EqualFold(step.Name, developerName) {
			return step
		}
		if label != "" && (strings.EqualFold(step.Label, label) || strings.EqualFold(step.Name, label)) {
			return step
		}
	}
	return nil
}

func normalizeXMLApprovers(assigned *AssignedApproverXML) []NormalizedApprovalApprover {
	if assigned == nil {
		return nil
	}
	approvers := make([]NormalizedApprovalApprover, 0, len(assigned.Approvers))
	for _, approver := range assigned.Approvers {
		approvers = append(approvers, NormalizedApprovalApprover{
			Name: strings.TrimSpace(approver.Name),
			Type: normalizeApproverType(approver.Type),
		})
	}
	return approvers
}

func normalizeXMLAllowedSubmitters(allowed []AllowedSubmitterXML) []NormalizedAllowedSubmitter {
	if len(allowed) == 0 {
		return nil
	}
	normalized := make([]NormalizedAllowedSubmitter, 0, len(allowed))
	for _, submitter := range allowed {
		if normalizedSubmitter, ok := normalizeAllowedSubmitter(submitter.Submitter, submitter.Type); ok {
			normalized = append(normalized, normalizedSubmitter)
		}
	}
	return normalized
}

func normalizeNextAutomatedApproverXML(nextAutomatedApprover *NextAutomatedApproverXML) *NormalizedNextAutomatedApprover {
	if nextAutomatedApprover == nil {
		return nil
	}
	userHierarchyField := strings.TrimSpace(nextAutomatedApprover.UserHierarchyField)
	if userHierarchyField == "" {
		return nil
	}
	return &NormalizedNextAutomatedApprover{
		UserHierarchyField:            userHierarchyField,
		UseApproverFieldOfRecordOwner: nextAutomatedApprover.UseApproverFieldOfRecordOwner != nil && *nextAutomatedApprover.UseApproverFieldOfRecordOwner,
	}
}

func normalizeJSONApprovers(approvers approverListJSON) []NormalizedApprovalApprover {
	if len(approvers) == 0 {
		return nil
	}
	normalized := make([]NormalizedApprovalApprover, 0, len(approvers))
	for _, approver := range approvers {
		normalized = append(normalized, NormalizedApprovalApprover{
			Name: strings.TrimSpace(approver.Name),
			Type: normalizeApproverType(approver.Type),
		})
	}
	return normalized
}

func normalizeJSONAllowedSubmitters(allowed allowedSubmitterListJSON) []NormalizedAllowedSubmitter {
	if len(allowed) == 0 {
		return nil
	}
	normalized := make([]NormalizedAllowedSubmitter, 0, len(allowed))
	for _, submitter := range allowed {
		if normalizedSubmitter, ok := normalizeAllowedSubmitter(submitter.Submitter, submitter.Type); ok {
			normalized = append(normalized, normalizedSubmitter)
		}
	}
	return normalized
}

func normalizeAllowedSubmitter(submitter, rawType string) (NormalizedAllowedSubmitter, bool) {
	normalizedType := normalizeSubmitterType(rawType)
	if normalizedType == "" {
		return NormalizedAllowedSubmitter{}, false
	}
	normalized := NormalizedAllowedSubmitter{
		Submitter: strings.TrimSpace(submitter),
		Type:      normalizedType,
	}
	switch normalized.Type {
	case constants.ApprovalSubmitterTypeAllInternalUsers,
		constants.ApprovalSubmitterTypeRecordCreator,
		constants.ApprovalSubmitterTypeRecordOwner:
		normalized.Submitter = ""
	}
	return normalized, true
}

func normalizeNextAutomatedApproverJSON(nextAutomatedApprover nextAutomatedApproverJSON) *NormalizedNextAutomatedApprover {
	userHierarchyField := strings.TrimSpace(nextAutomatedApprover.UserHierarchyField)
	if userHierarchyField == "" {
		return nil
	}
	return &NormalizedNextAutomatedApprover{
		UserHierarchyField:            userHierarchyField,
		UseApproverFieldOfRecordOwner: bool(nextAutomatedApprover.UseApproverFieldOfRecordOwner),
	}
}

func normalizeCriteriaXML(criteria *CriteriaXML) *NormalizedCriteria {
	if criteria == nil {
		return nil
	}
	normalized := &NormalizedCriteria{
		Formula:       strings.TrimSpace(criteria.Formula),
		BooleanFilter: strings.TrimSpace(criteria.BooleanFilter),
		CriteriaItems: make([]NormalizedCriteriaItem, 0, len(criteria.CriteriaItems)),
	}
	for _, item := range criteria.CriteriaItems {
		normalized.CriteriaItems = append(normalized.CriteriaItems, NormalizedCriteriaItem{
			Field:     strings.TrimSpace(item.Field),
			Operation: strings.TrimSpace(item.Operation),
			Value:     strings.TrimSpace(item.Value),
		})
	}
	if normalized.Formula == "" && normalized.BooleanFilter == "" && len(normalized.CriteriaItems) == 0 {
		return nil
	}
	return normalized
}

func normalizeCriteriaJSON(criteria *criteriaJSON) *NormalizedCriteria {
	if criteria == nil {
		return nil
	}
	normalized := &NormalizedCriteria{
		Formula:       strings.TrimSpace(criteria.Formula),
		BooleanFilter: strings.TrimSpace(criteria.BooleanFilter),
		CriteriaItems: make([]NormalizedCriteriaItem, 0, len(criteria.CriteriaItems)),
	}
	for _, item := range criteria.CriteriaItems {
		value := ""
		if item.Value.Valid {
			value = strings.TrimSpace(item.Value.String)
		}
		normalized.CriteriaItems = append(normalized.CriteriaItems, NormalizedCriteriaItem{
			Field:     strings.TrimSpace(item.Field),
			Operation: strings.TrimSpace(item.Operation),
			Value:     value,
		})
	}
	if normalized.Formula == "" && normalized.BooleanFilter == "" && len(normalized.CriteriaItems) == 0 {
		return nil
	}
	return normalized
}

func normalizeApprovalMode(assigned *AssignedApproverXML) string {
	if assigned == nil {
		return normalizeApprovalModeValue("")
	}
	return normalizeApprovalModeValue(assigned.WhenMultipleApprovers)
}

func normalizeApprovalModeValue(raw string) string {
	return constants.NormalizeApprovalMode(raw)
}

func normalizeRejectBehaviorXML(rejectBehavior *RejectBehaviorXML) string {
	if rejectBehavior == nil {
		return ""
	}
	return normalizeRejectBehaviorValue(rejectBehavior.Type)
}

func normalizeRejectBehaviorValue(raw string) string {
	return constants.NormalizeApprovalRejectBehavior(raw)
}

func normalizeApproverType(raw string) string {
	return constants.NormalizeApprovalApproverType(raw)
}

func normalizeSubmitterType(raw string) string {
	return constants.NormalizeApprovalSubmitterType(raw)
}

func decodeOneOrMany[T any](data []byte, out *[]T) error {
	trimmed := bytes.TrimSpace(data)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
		*out = nil
		return nil
	}
	if trimmed[0] == '[' {
		return json.Unmarshal(trimmed, out)
	}
	var item T
	if err := json.Unmarshal(trimmed, &item); err != nil {
		return err
	}
	*out = []T{item}
	return nil
}
