package runtimeapi

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

	sharedErrors "github.com/nexuscrm/shared/pkg/errors"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

// classifyPlatformEngineError projects neutral engine conditions into the
// common private Platform failure grammar. invalidComponent is selected by the
// capability handler because the wire contract intentionally permits a
// different finite invalid-request vocabulary for each capability.
//
// Cancellation is attributed to the caller only when the request context
// proves it. An engine returning a context sentinel while the request remains
// live is an owner-side availability failure. Infrastructure ownership is
// resolved before domain errors so an internal wrapper around a client error
// cannot disclose the wrapped condition.
func classifyPlatformEngineError(
	ctx context.Context,
	err error,
	invalidComponent commonsemantic.InvalidComponent,
) error {
	if contextFailure, owned := classifyEngineContextOwnership(ctx, err); owned {
		return contextFailure
	}
	switch sharedErrors.ClassifyInfrastructure(err) {
	case sharedErrors.InfrastructureConditionRetriable:
		failure, _ := classifyRetriableEngineCondition(err)
		return failure
	case sharedErrors.InfrastructureConditionInternal:
		return &commonsemantic.Failure{Category: commonsemantic.FailureInternal}
	}
	for current := err; current != nil; current = errors.Unwrap(current) {
		if _, joined := current.(interface{ Unwrap() []error }); joined {
			return &commonsemantic.Failure{Category: commonsemantic.FailureInternal}
		}
	}
	var semanticFailure *commonsemantic.Failure
	if errors.As(err, &semanticFailure) {
		if !commonsemantic.ValidFailureConflictReason(semanticFailure) {
			return &commonsemantic.Failure{Category: commonsemantic.FailureInternal}
		}
		return semanticFailure
	}
	if sharedErrors.IsUnauthenticated(err) {
		return &commonsemantic.Failure{Category: commonsemantic.FailureUnauthenticated}
	}
	if sharedErrors.IsPermissionDenied(err) {
		return &commonsemantic.Failure{Category: commonsemantic.FailureForbidden}
	}
	if sharedErrors.IsConflict(err) {
		failure := &commonsemantic.Failure{Category: commonsemantic.FailureConflict}
		var reason interface {
			ConflictReason() commonsemantic.ConflictReason
		}
		if errors.As(err, &reason) {
			failure.ConflictReason = reason.ConflictReason()
		}
		if !commonsemantic.ValidFailureConflictReason(failure) {
			return &commonsemantic.Failure{Category: commonsemantic.FailureInternal}
		}
		return failure
	}
	var domain *sharedErrors.DomainWireError
	if errors.As(err, &domain) && domain != nil {
		switch domain.Kind {
		case sharedErrors.WireInvalidReference,
			sharedErrors.WireFieldIntegrity,
			sharedErrors.WireInvalidOperation,
			sharedErrors.WireInvalidField,
			sharedErrors.WireMalformedRequest:
			return &commonsemantic.Failure{
				Category:         commonsemantic.FailureInvalidRequest,
				InvalidComponent: invalidComponent,
			}
		case sharedErrors.WireNotFound:
			return &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
		default:
			return &commonsemantic.Failure{Category: commonsemantic.FailureInternal}
		}
	}
	if sharedErrors.IsNotFound(err) {
		return &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
	}
	var statuser interface{ HTTPStatus() int }
	if errors.As(err, &statuser) {
		switch statuser.HTTPStatus() {
		case http.StatusBadRequest:
			return &commonsemantic.Failure{
				Category:         commonsemantic.FailureInvalidRequest,
				InvalidComponent: invalidComponent,
			}
		case http.StatusUnauthorized:
			return &commonsemantic.Failure{Category: commonsemantic.FailureUnauthenticated}
		case http.StatusForbidden:
			return &commonsemantic.Failure{Category: commonsemantic.FailureForbidden}
		case http.StatusNotFound:
			return &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
		case http.StatusConflict:
			return &commonsemantic.Failure{Category: commonsemantic.FailureConflict}
		}
	}
	return &commonsemantic.Failure{Category: commonsemantic.FailureInternal}
}

// classifyEngineContextOwnership keeps cancellation attribution on one read
// path. Only the request context can prove caller cancellation or deadline;
// an engine-owned error chain containing either sentinel is an availability
// failure while that request remains live.
func classifyEngineContextOwnership(ctx context.Context, err error) (error, bool) {
	if ctxErr := ctx.Err(); ctxErr != nil {
		return ctxErr, true
	}
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return &commonsemantic.Failure{Category: commonsemantic.FailureUnavailable}, true
	}
	return nil, false
}

// classifyRetriableEngineCondition preserves the engine owner's typed
// backpressure decision before generic InternalCondition handling. Some
// fail-closed engine errors intentionally implement both interfaces: they are
// internal implementation conditions, but callers may safely retry them with
// backoff. Treating InternalCondition first turns load shedding into a
// non-retriable 500 at the process boundary.
func classifyRetriableEngineCondition(err error) (*commonsemantic.Failure, bool) {
	if sharedErrors.ClassifyInfrastructure(err) != sharedErrors.InfrastructureConditionRetriable {
		return nil, false
	}
	failure := &commonsemantic.Failure{Category: commonsemantic.FailureUnavailable}
	seconds := owningRetryAfterSeconds(err)
	if seconds > 0 {
		const millisPerSecond = uint64(1000)
		if uint64(seconds) > ^uint64(0)/millisPerSecond {
			failure.RetryAfterMillis = ^uint64(0)
		} else {
			failure.RetryAfterMillis = uint64(seconds) * millisPerSecond
		}
	}
	return failure, true
}

// owningRetryAfterSeconds follows the same first-classified-owner rule as
// sharedErrors.ClassifyInfrastructure. A transparent single unwrap may expose
// the retriable owner. A joined error has no single owner, so it deliberately
// contributes no retry hint even when every branch is retriable.
func owningRetryAfterSeconds(err error) int {
	for err != nil {
		if retriable, ok := err.(interface{ RetriableCondition() bool }); ok && retriable.RetriableCondition() {
			if retryAfter, ok := err.(interface{ RetryAfterSeconds() int }); ok {
				return retryAfter.RetryAfterSeconds()
			}
			return 0
		}
		if internal, ok := err.(interface{ InternalCondition() bool }); ok && internal.InternalCondition() {
			return 0
		}
		if _, joined := err.(interface{ Unwrap() []error }); joined {
			return 0
		}
		err = errors.Unwrap(err)
	}
	return 0
}
