package federatedloginv1

import (
	"context"
	"errors"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

// NormalizeBeginOperationFailure enforces the finite failure grammar for a
// Begin owner result after ValidateBeginRequest has succeeded. Request decode
// and validation failures are not owner results and must be projected through
// the request-status path instead.
func NormalizeBeginOperationFailure(err error) error {
	return normalizeOperationFailure(err, false)
}

// NormalizeConsumeOperationFailure enforces the finite failure grammar for a
// Consume owner result after ValidateConsumeRequest has succeeded. Every
// category that could encode whether a state exists, is current, is bound to
// the redirect, or was already used collapses to a fresh, detail-free
// UNAUTHENTICATED failure.
//
// Request decode and validation failures are not owner results and must be
// projected through the request-status path instead.
func NormalizeConsumeOperationFailure(err error) error {
	return normalizeOperationFailure(err, true)
}

func normalizeOperationFailure(err error, consume bool) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, context.Canceled) {
		return &Failure{Category: FailureCanceled}
	}
	if errors.Is(err, context.DeadlineExceeded) {
		return &Failure{Category: FailureDeadlineExceeded}
	}

	var failure *Failure
	if !errors.As(err, &failure) || failure == nil {
		return internalFailure()
	}
	if !common.ValidFailureRetryAfter(failure) {
		return internalFailure()
	}

	if consume {
		switch failure.Category {
		case FailureInvalidRequest, FailureUnauthenticated, FailureForbidden,
			FailureMissing, FailureStaleState:
			return &Failure{Category: FailureUnauthenticated}
		}
	}

	switch failure.Category {
	case FailureUnauthenticated, FailureForbidden, FailureCanceled, FailureDeadlineExceeded:
		return &Failure{Category: failure.Category}
	case FailureResourceExhausted:
		// Trusted transports may reject work before the operation starts and
		// therefore cannot attach a semantic LimitKind. Preserve that bare
		// fail-fast admission outcome instead of misclassifying it as INTERNAL.
		if failure.LimitKind == "" {
			return &Failure{Category: FailureResourceExhausted}
		}
		if failure.LimitKind != LimitAdmission {
			return internalFailure()
		}
		return &Failure{
			Category:         FailureResourceExhausted,
			LimitKind:        LimitAdmission,
			Maximum:          failure.Maximum,
			RetryAfterMillis: failure.RetryAfterMillis,
		}
	case FailureIncompatible:
		if !validOrEmptyCompatibility(failure) {
			return internalFailure()
		}
		return &Failure{
			Category:           FailureIncompatible,
			RequiredCapability: CapabilityName,
			SupportedMajor:     ProtocolMajor,
			SupportedMinorMin:  ProtocolMinor,
			SupportedMinorMax:  ProtocolMinor,
		}
	case FailureStaleState:
		if failure.StaleAxis != StaleAxisSecurity {
			return internalFailure()
		}
		return &Failure{
			Category:         FailureStaleState,
			StaleAxis:        StaleAxisSecurity,
			RequiredRevision: failure.RequiredRevision,
			ServedRevision:   failure.ServedRevision,
		}
	case FailureUnavailable:
		return &Failure{Category: FailureUnavailable, RetryAfterMillis: failure.RetryAfterMillis}
	case FailureInternal:
		return &Failure{Category: FailureInternal, RequestID: failure.RequestID}
	default:
		return internalFailure()
	}
}

func internalFailure() error {
	return &Failure{Category: FailureInternal}
}

func validOrEmptyCompatibility(failure *Failure) bool {
	if failure.RequiredCapability == "" && failure.SupportedMajor == 0 &&
		failure.SupportedMinorMin == 0 && failure.SupportedMinorMax == 0 {
		return true
	}
	return failure.RequiredCapability == CapabilityName &&
		failure.SupportedMajor == ProtocolMajor &&
		failure.SupportedMinorMin <= ProtocolMinor &&
		failure.SupportedMinorMax >= ProtocolMinor
}
