package automation

import (
	"context"
	"errors"
	"net/http"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/contract"
	sharedErrors "github.com/nexuscrm/shared/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

// flowExecutionResultErrorOrigin is transient execution evidence. The public
// FlowExecutionResult carrier deliberately has no origin field, and authored
// status codes are unrestricted, so a raw code string can never prove that an
// error is an engine-owned neutral condition.
type flowExecutionResultErrorOrigin uint8

const (
	flowExecutionResultErrorOriginUnknown flowExecutionResultErrorOrigin = iota
	flowExecutionResultErrorOriginNeutralEngine
	flowExecutionResultErrorOriginAuthored
)

type flowExecutionResultErrorOriginKey struct{}

type flowExecutionResultErrorOriginCollector struct {
	origin flowExecutionResultErrorOrigin
}

func withFlowExecutionResultErrorOrigin(ctx context.Context) (context.Context, *flowExecutionResultErrorOriginCollector) {
	collector := &flowExecutionResultErrorOriginCollector{}
	return context.WithValue(ctx, flowExecutionResultErrorOriginKey{}, collector), collector
}

func publishFlowExecutionResultErrorOrigin(ctx context.Context, origin flowExecutionResultErrorOrigin) {
	collector, _ := ctx.Value(flowExecutionResultErrorOriginKey{}).(*flowExecutionResultErrorOriginCollector)
	if collector == nil || origin == flowExecutionResultErrorOriginUnknown {
		return
	}
	if collector.origin == flowExecutionResultErrorOriginUnknown || collector.origin == origin {
		collector.origin = origin
		return
	}
	// Conflicting producer evidence must fail closed: disable neutral parsing.
	collector.origin = flowExecutionResultErrorOriginAuthored
}

// flowClientValidationFault records provenance that cannot be inferred from
// ValidationError alone: the complete client-validation tree came from a
// record-mutation boundary (or from a child Flow result that had already made
// that presentation decision). Ordinary Flow configuration/action validation
// errors never receive this marker.
type flowClientValidationFault struct {
	cause error
}

func (e *flowClientValidationFault) Error() string {
	if e == nil || e.cause == nil {
		return "flow client validation"
	}
	return e.cause.Error()
}

func (e *flowClientValidationFault) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.cause
}

func newFlowClientValidationFault(err error) error {
	if err == nil || apperrors.IsInternal(err) {
		return err
	}
	if hasFlowClientValidationFault(err) {
		return err
	}
	if _, ok := authoredCustomFlowExecutionErrors(err); ok {
		if _, clientVisible := apperrors.AsClientValidationError(err); !clientVisible {
			return err
		}
		return &flowClientValidationFault{cause: err}
	}
	clientErr, ok := apperrors.AsClientValidationError(err)
	if !ok {
		return err
	}
	return &flowClientValidationFault{cause: clientErr}
}

func flowRecordMutationValidation(err error) error {
	return newFlowClientValidationFault(err)
}

func nestedFlowClientValidation(err error) error {
	return newFlowClientValidationFault(err)
}

// flowClientValidationForPresentation is the single Flow surfacing gate. It
// admits explicitly marked record-mutation/child-Flow validation and complete
// validation-rule-only trees (the legacy Flow contract). A lone unmarked
// ValidationError/FLS denial and every InternalError remain FLOW_ERROR.
func flowClientValidationForPresentation(err error) (error, bool) {
	if err == nil || apperrors.IsInternal(err) {
		return nil, false
	}
	clientErr, ok := apperrors.AsClientValidationError(err)
	if !ok {
		return nil, false
	}
	leaves, ok := apperrors.AsClientValidationErrors(clientErr)
	if !ok || len(leaves) == 0 {
		return nil, false
	}
	if hasFlowClientValidationFault(err) {
		return err, true
	}

	for _, leaf := range leaves {
		if _, ok := leaf.(sharedErrors.ValidationRuleFailuresConditioner); !ok {
			return nil, false
		}
	}
	return clientErr, true
}

// hasFlowClientValidationFault walks only transparent single-error wrappers.
// A joined outer tree cannot borrow trusted provenance from one child and hide
// an unmarked sibling.
func hasFlowClientValidationFault(err error) bool {
	for current := err; current != nil; current = errors.Unwrap(current) {
		if _, ok := current.(*flowClientValidationFault); ok {
			return true
		}
		if _, joined := current.(interface{ Unwrap() []error }); joined {
			return false
		}
	}
	return false
}

func flowExecutionErrorsForPresentation(err error) ([]models.FlowExecutionError, bool) {
	clientErr, ok := flowClientValidationForPresentation(err)
	if !ok {
		return nil, false
	}
	return flowExecutionErrorsFromClientValidation(clientErr)
}

// flowExecutionErrorsFromClientValidation projects every ordered validation
// leaf into the Flow result boundary. The caller must not use a partial result:
// false means the complete tree could not be projected safely.
func flowExecutionErrorsFromClientValidation(err error) ([]models.FlowExecutionError, bool) {
	leaves, ok := apperrors.AsClientValidationErrors(err)
	if !ok {
		return nil, false
	}

	projected := make([]models.FlowExecutionError, 0, len(leaves))
	for _, leaf := range leaves {
		if failures, ok := apperrors.AsValidationRuleFailures(leaf); ok {
			for _, failure := range failures {
				projected = append(projected, models.FlowExecutionError{
					StatusCode: contract.FlowExecutionErrorCodeValidation,
					Message:    failure.Message,
					Fields:     nonBlankFlowValidationFields([]string{failure.Field}),
				})
			}
			continue
		}
		if fields, message, ok := sharedErrors.FieldWriteDeniedDetails(leaf); ok {
			projected = append(projected, models.FlowExecutionError{
				StatusCode: contract.FlowExecutionErrorCodeFieldWriteDenied,
				Message:    clientValidationLeafMessage(leaf, message),
				Fields:     nonBlankFlowValidationFields(fields),
			})
			continue
		}
		if field, message, ok := sharedErrors.ValidationDetails(leaf); ok {
			projected = append(projected, models.FlowExecutionError{
				StatusCode: contract.FlowExecutionErrorCodeValidation,
				Message:    clientValidationLeafMessage(leaf, message),
				Fields:     nonBlankFlowValidationFields([]string{field}),
			})
			continue
		}
		return nil, false
	}
	return projected, len(projected) > 0
}

func clientValidationLeafMessage(err error, message string) string {
	if strings.TrimSpace(message) != "" {
		return message
	}
	if err == nil {
		return ""
	}
	return err.Error()
}

func nonBlankFlowValidationFields(fields []string) []string {
	projected := make([]string, 0, len(fields))
	for _, field := range fields {
		if field = strings.TrimSpace(field); field != "" {
			projected = append(projected, field)
		}
	}
	return projected
}

func cloneFlowExecutionErrors(flowErrors []models.FlowExecutionError) []models.FlowExecutionError {
	cloned := make([]models.FlowExecutionError, len(flowErrors))
	for i, flowErr := range flowErrors {
		cloned[i] = flowErr
		cloned[i].Fields = append([]string(nil), flowErr.Fields...)
	}
	return cloned
}

// flowCustomErrorFaultForPresentation is the single authored-fault read path.
// An outer InternalError owns the boundary classification even when its cause
// is an authored fault (for example after rollback itself fails), so never
// unwrap through an internal failure to recover client presentation data.
func flowCustomErrorFaultForPresentation(err error) (*FlowCustomErrorFault, bool) {
	if err == nil || apperrors.IsInternal(err) {
		return nil, false
	}
	var fault *FlowCustomErrorFault
	if !errors.As(err, &fault) {
		return nil, false
	}
	return fault, true
}

// internalErrorWrapsFlowCustomErrorFault detects only the fail-closed veto. It
// never returns or presents the authored fault hidden below the internal owner.
func internalErrorWrapsFlowCustomErrorFault(err error) bool {
	if !apperrors.IsInternal(err) {
		return false
	}
	var fault *FlowCustomErrorFault
	return errors.As(err, &fault)
}

func authoredCustomFlowExecutionErrors(err error) ([]models.FlowExecutionError, bool) {
	fault, ok := flowCustomErrorFaultForPresentation(err)
	if !ok || len(fault.resultErrors) == 0 {
		return nil, false
	}
	return cloneFlowExecutionErrors(fault.resultErrors), true
}

func (f *FlowCustomErrorFault) Unwrap() error {
	if f == nil {
		return nil
	}
	return f.cause
}

func (f *FlowCustomErrorFault) HTTPStatus() int {
	return http.StatusBadRequest
}

func (f *FlowCustomErrorFault) Code() string {
	return string(sharedErrors.ErrValidation)
}

func clientValidationErrorFromFlowExecutionResult(result *models.FlowExecutionResult, ruleName string, origin flowExecutionResultErrorOrigin) (error, bool) {
	if result == nil || !result.IsCustomError || len(result.Errors) == 0 || origin != flowExecutionResultErrorOriginNeutralEngine {
		return nil, false
	}
	leaves := make([]error, 0, len(result.Errors))
	for _, flowErr := range result.Errors {
		switch strings.ToUpper(strings.TrimSpace(flowErr.StatusCode)) {
		case contract.FlowExecutionErrorCodeValidation:
			fields := nonBlankFlowValidationFields(flowErr.Fields)
			if len(fields) == 0 {
				leaves = append(leaves, apperrors.NewValidationRuleError(ruleName, flowErr.Message))
				continue
			}
			for _, field := range fields {
				failure := apperrors.NewValidationRuleError(ruleName, flowErr.Message)
				failure.Field = field
				leaves = append(leaves, failure)
			}
		case contract.FlowExecutionErrorCodeFieldWriteDenied:
			leaves = append(leaves, apperrors.NewFLSDeniedError(
				nonBlankFlowValidationFields(flowErr.Fields),
				flowErr.Message,
			))
		default:
			return nil, false
		}
	}
	combined := apperrors.NewClientValidationErrors(leaves...)
	return combined, combined != nil
}

// fallbackClientValidationErrorFromFlowExecutionResult preserves the complete
// ordered payload of an authored custom-error result without interpreting its
// dialect-owned status codes. Before-save Flow compatibility presents every
// such error as a record-blocking validation failure.
func fallbackClientValidationErrorFromFlowExecutionResult(result *models.FlowExecutionResult, ruleName string) error {
	if result == nil || !result.IsCustomError || len(result.Errors) == 0 {
		return nil
	}
	failures := make([]apperrors.ValidationRuleFailure, 0, len(result.Errors))
	for _, flowErr := range result.Errors {
		fields := nonBlankFlowValidationFields(flowErr.Fields)
		if len(fields) == 0 {
			failures = append(failures, apperrors.ValidationRuleFailure{
				RuleName: ruleName,
				Message:  flowErr.Message,
			})
			continue
		}
		for _, field := range fields {
			failures = append(failures, apperrors.ValidationRuleFailure{
				RuleName: ruleName,
				Message:  flowErr.Message,
				Field:    field,
			})
		}
	}
	validationErr := apperrors.NewValidationRuleFailures(failures)
	if validationErr == nil {
		return nil
	}
	fault := authoredCustomFlowErrorFromFlowExecutionResult(result)
	if fault == nil {
		return nil
	}
	fault.cause = validationErr
	return fault
}

func authoredCustomFlowErrorFromFlowExecutionResult(result *models.FlowExecutionResult) *FlowCustomErrorFault {
	if result == nil || !result.IsCustomError || len(result.Errors) == 0 {
		return nil
	}
	flowErrors := cloneFlowExecutionErrors(result.Errors)
	return &FlowCustomErrorFault{
		Message:      flowErrors[0].Message,
		StatusCode:   flowErrors[0].StatusCode,
		resultErrors: flowErrors,
	}
}

func errorFromFailedFlowExecutionResult(result *models.FlowExecutionResult, fallback, ruleName string, origin flowExecutionResultErrorOrigin) error {
	if validationErr, ok := clientValidationErrorFromFlowExecutionResult(result, ruleName, origin); ok {
		// The child already made the client-presentation decision. Restore that
		// provenance before the error re-enters the parent's generic element
		// boundary; the reconstructed concrete ValidationError shape alone is
		// not sufficient evidence.
		return nestedFlowClientValidation(validationErr)
	}
	if authoredErr := authoredCustomFlowErrorFromFlowExecutionResult(result); authoredErr != nil {
		return authoredErr
	}
	messages := make([]string, 0)
	if result != nil {
		messages = make([]string, 0, len(result.Errors))
		for _, flowErr := range result.Errors {
			if message := strings.TrimSpace(flowErr.Message); message != "" {
				messages = append(messages, message)
			}
		}
	}
	if len(messages) == 0 {
		return errors.New(fallback)
	}
	return errors.New(strings.Join(messages, "; "))
}
