package approvalir

import (
	"fmt"
	"strings"
)

// ApproverKind selects how an approval-step actor is resolved.
type ApproverKind string

const (
	ApproverKindRelatedUserField   ApproverKind = "related_user_field"
	ApproverKindUserHierarchyField ApproverKind = "user_hierarchy_field"
	ApproverKindPreviousApprover   ApproverKind = "previous_approver"
	ApproverKindAdHoc              ApproverKind = "ad_hoc"
	ApproverKindUser               ApproverKind = "user"
	ApproverKindQueue              ApproverKind = "queue"
	ApproverKindRole               ApproverKind = "role"
	ApproverKindHierarchy          ApproverKind = "hierarchy"
)

// Valid reports whether k is a defined ApproverKind.
func (k ApproverKind) Valid() bool {
	switch k {
	case ApproverKindRelatedUserField, ApproverKindUserHierarchyField,
		ApproverKindPreviousApprover, ApproverKindAdHoc, ApproverKindUser,
		ApproverKindQueue, ApproverKindRole, ApproverKindHierarchy:
		return true
	default:
		return false
	}
}

// MultipleApproverMode defines how a step with multiple approvers completes.
type MultipleApproverMode string

const (
	MultipleApproverModeFirstResponse MultipleApproverMode = "first_response"
	MultipleApproverModeUnanimous     MultipleApproverMode = "unanimous"
)

// Valid reports whether m is a defined MultipleApproverMode.
func (m MultipleApproverMode) Valid() bool {
	return m == MultipleApproverModeFirstResponse || m == MultipleApproverModeUnanimous
}

// RejectBehavior defines the transition caused by rejecting a step.
type RejectBehavior string

const (
	RejectBehaviorBackToPrevious  RejectBehavior = "back_to_previous"
	RejectBehaviorBackToSubmitter RejectBehavior = "back_to_submitter"
	RejectBehaviorRejectRequest   RejectBehavior = "reject_request"
	RejectBehaviorFinalRejection  RejectBehavior = "final_rejection"
)

// Valid reports whether b is a defined RejectBehavior.
func (b RejectBehavior) Valid() bool {
	switch b {
	case RejectBehaviorBackToPrevious, RejectBehaviorBackToSubmitter,
		RejectBehaviorRejectRequest, RejectBehaviorFinalRejection:
		return true
	default:
		return false
	}
}

// IfCriteriaNotMet defines the transition when a step predicate is false.
type IfCriteriaNotMet string

const (
	IfCriteriaNotMetGotoNextStep IfCriteriaNotMet = "goto_next_step"
	IfCriteriaNotMetApprove      IfCriteriaNotMet = "approve"
	IfCriteriaNotMetReject       IfCriteriaNotMet = "reject"
)

// Valid reports whether b is a defined IfCriteriaNotMet value.
func (b IfCriteriaNotMet) Valid() bool {
	return b == IfCriteriaNotMetGotoNextStep || b == IfCriteriaNotMetApprove || b == IfCriteriaNotMetReject
}

// HierarchyInitialPrincipal selects the principal at which hierarchy traversal starts.
type HierarchyInitialPrincipal string

const (
	HierarchyInitialPrincipalSubmitter   HierarchyInitialPrincipal = "submitter"
	HierarchyInitialPrincipalRecordOwner HierarchyInitialPrincipal = "record_owner"
)

// Valid reports whether p is a defined HierarchyInitialPrincipal.
func (p HierarchyInitialPrincipal) Valid() bool {
	return p == HierarchyInitialPrincipalSubmitter || p == HierarchyInitialPrincipalRecordOwner
}

// HierarchyRouting describes the complete, product-neutral hierarchy traversal rule.
type HierarchyRouting struct {
	UserHierarchyField string
	InitialPrincipal   HierarchyInitialPrincipal
}

// Definition is the neutral execution view of one approval definition. Label
// is optional because the containing metadata record may own the definition's
// display identity; step labels remain required for executable step semantics.
type Definition struct {
	Label                    string
	Active                   bool
	Description              string
	AllowRecall              bool
	Policy                   ApprovalPolicy
	FinalApprovalRecordLock  bool
	FinalRejectionRecordLock bool
	EntryCriteria            *Criteria
	ProcessOrder             *int
	HierarchyRouting         *HierarchyRouting
	Steps                    []Step
	InitialSubmissionActions []Action
	StepApprovalActions      []Action
	FinalApprovalActions     []Action
	FinalRejectionActions    []Action
	RecallActions            []Action
	ReassignActions          []Action
}

// Step is one ordered step in an approval definition.
type Step struct {
	Name                 string
	Label                string
	Description          string
	AllowDelegate        bool
	AllowComments        *bool
	RequireComments      *bool
	Approvers            []Approver
	MultipleApproverMode MultipleApproverMode
	RejectBehavior       RejectBehavior
	Criteria             *Criteria
	IfCriteriaNotMet     IfCriteriaNotMet
	ApprovalActions      []Action
	RejectionActions     []Action
}

// Approver identifies one configured approver selection.
type Approver struct {
	Kind      ApproverKind
	Reference string
}

// Validate rejects incomplete, ambiguous, or unroutable approval semantics.
func (d *Definition) Validate() error {
	return d.validate(nil)
}

// ValidateWithFormulaValidator validates the complete neutral definition and
// delegates generic formula syntax to the platform's single formula authority.
func (d *Definition) ValidateWithFormulaValidator(validator FormulaValidator) error {
	return d.validate(validator)
}

func (d *Definition) validate(formulaValidator FormulaValidator) error {
	if d == nil {
		return fmt.Errorf("definition is nil")
	}
	if err := d.Policy.Validate(); err != nil {
		return fmt.Errorf("policy: %w", err)
	}
	if d.ProcessOrder != nil && *d.ProcessOrder <= 0 {
		return fmt.Errorf("process order must be positive")
	}
	if d.EntryCriteria != nil {
		if err := d.EntryCriteria.validate(formulaValidator); err != nil {
			return fmt.Errorf("entry criteria: %w", err)
		}
	}
	if d.HierarchyRouting != nil {
		if err := d.HierarchyRouting.validate(); err != nil {
			return fmt.Errorf("hierarchy routing: %w", err)
		}
	}
	if d.Active && len(d.Steps) == 0 {
		return fmt.Errorf("active definition requires at least one step")
	}
	names := make(map[string]struct{}, len(d.Steps))
	context := stepValidationContext{hierarchyRouting: d.HierarchyRouting, formulaValidator: formulaValidator}
	for index := range d.Steps {
		name := strings.ToLower(strings.TrimSpace(d.Steps[index].Name))
		if name == "" {
			return fmt.Errorf("step %d: name is required", index)
		}
		if _, exists := names[name]; exists {
			return fmt.Errorf("step %d: duplicate normalized name %q", index, name)
		}
		names[name] = struct{}{}
		if err := d.Steps[index].validate(context); err != nil {
			return fmt.Errorf("step %d: %w", index, err)
		}
		if d.Steps[index].Criteria == nil || d.Steps[index].IfCriteriaNotMet != IfCriteriaNotMetGotoNextStep {
			context.guaranteedPriorActor = true
		}
	}
	groups := []struct {
		name    string
		actions []Action
	}{
		{"initial submission actions", d.InitialSubmissionActions},
		{"step approval actions", d.StepApprovalActions},
		{"final approval actions", d.FinalApprovalActions},
		{"final rejection actions", d.FinalRejectionActions},
		{"recall actions", d.RecallActions},
		{"reassign actions", d.ReassignActions},
	}
	for _, group := range groups {
		if err := validateActions(group.actions); err != nil {
			return fmt.Errorf("%s: %w", group.name, err)
		}
	}
	return nil
}

type stepValidationContext struct {
	hierarchyRouting     *HierarchyRouting
	guaranteedPriorActor bool
	formulaValidator     FormulaValidator
}

func (r HierarchyRouting) validate() error {
	if strings.TrimSpace(r.UserHierarchyField) == "" {
		return fmt.Errorf("user hierarchy field is required")
	}
	if !r.InitialPrincipal.Valid() {
		return fmt.Errorf("invalid initial principal %q", r.InitialPrincipal)
	}
	return nil
}

func (s *Step) validate(context stepValidationContext) error {
	if strings.TrimSpace(s.Label) == "" {
		return fmt.Errorf("label is required")
	}
	if s.AllowComments != nil && s.RequireComments != nil && !*s.AllowComments && *s.RequireComments {
		return fmt.Errorf("comments cannot be required when comments are disallowed")
	}
	if len(s.Approvers) == 0 {
		return fmt.Errorf("requires at least one approver")
	}
	if len(s.Approvers) == 1 && s.MultipleApproverMode != "" {
		return fmt.Errorf("one approver must not carry a multiple-approver mode")
	}
	if len(s.Approvers) > 1 && !s.MultipleApproverMode.Valid() {
		return fmt.Errorf("multiple approvers require a valid multiple-approver mode")
	}
	if !s.RejectBehavior.Valid() {
		return fmt.Errorf("invalid reject behavior %q", s.RejectBehavior)
	}
	if s.RejectBehavior == RejectBehaviorBackToPrevious && !context.guaranteedPriorActor {
		return fmt.Errorf("back-to-previous requires guaranteed prior actor context")
	}
	if (s.Criteria == nil) != (s.IfCriteriaNotMet == "") {
		return fmt.Errorf("criteria and if-criteria-not-met must be specified together")
	}
	if s.Criteria != nil {
		if !s.IfCriteriaNotMet.Valid() {
			return fmt.Errorf("invalid if-criteria-not-met behavior %q", s.IfCriteriaNotMet)
		}
		if err := s.Criteria.validate(context.formulaValidator); err != nil {
			return fmt.Errorf("criteria: %w", err)
		}
	}
	for index := range s.Approvers {
		if err := s.Approvers[index].validate(context); err != nil {
			return fmt.Errorf("approver %d: %w", index, err)
		}
	}
	if err := validateActions(s.ApprovalActions); err != nil {
		return fmt.Errorf("approval actions: %w", err)
	}
	if err := validateActions(s.RejectionActions); err != nil {
		return fmt.Errorf("rejection actions: %w", err)
	}
	return nil
}

func (a Approver) validate(context stepValidationContext) error {
	if !a.Kind.Valid() {
		return fmt.Errorf("invalid kind %q", a.Kind)
	}
	reference := strings.TrimSpace(a.Reference)
	switch a.Kind {
	case ApproverKindHierarchy, ApproverKindAdHoc, ApproverKindPreviousApprover:
		if reference != "" {
			return fmt.Errorf("kind %q must not carry a reference", a.Kind)
		}
	case ApproverKindRelatedUserField, ApproverKindUserHierarchyField,
		ApproverKindUser, ApproverKindQueue, ApproverKindRole:
		if reference == "" {
			return fmt.Errorf("kind %q requires a reference", a.Kind)
		}
	}
	if a.Kind == ApproverKindHierarchy && context.hierarchyRouting == nil {
		return fmt.Errorf("hierarchy kind requires definition hierarchy routing")
	}
	if a.Kind == ApproverKindPreviousApprover && !context.guaranteedPriorActor {
		return fmt.Errorf("previous-approver kind requires guaranteed prior actor context")
	}
	return nil
}
