package catalogtenant

import (
	"fmt"

	tenantauthorityv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/tenantlayoutbundleauthorityv1"
)

func decideNamespace(
	request tenantauthorityv1.TenantNamespaceCommandRequest,
	observed ValueReference,
	hasActiveBinding bool,
	hasActiveSlot bool,
) Result {
	if observed != namespaceExpectedReference(request.ExpectedPriorNamespace) {
		return ResultExpectedPriorMismatch
	}
	if request.Operation == tenantauthorityv1.NamespaceOperationTombstone {
		if hasActiveBinding {
			return ResultActiveBinding
		}
		if hasActiveSlot {
			return ResultActiveSlot
		}
	}
	return ResultApplied
}

func decideBinding(
	request tenantauthorityv1.TenantOrgNamespaceBindingCommandRequest,
	namespace namespaceCurrent,
	namespacePresent bool,
	current bindingCurrent,
	currentPresent bool,
	blocksSlotRetirement bool,
) Result {
	if !activeNamespaceDependencyMatches(
		namespace,
		namespacePresent,
		request.DesiredBindingValue.TenantNamespaceID,
		request.ExpectedNamespaceReservationDigest,
	) {
		return ResultNamespaceDependencyMismatch
	}
	if !bindingExpectedPriorMatches(request, current, currentPresent) {
		return ResultExpectedPriorMismatch
	}
	if request.Operation == tenantauthorityv1.BindingOperationTombstone &&
		blocksSlotRetirement {
		return ResultActiveSlot
	}
	return ResultApplied
}

func bindingExpectedPriorMatches(
	request tenantauthorityv1.TenantOrgNamespaceBindingCommandRequest,
	current bindingCurrent,
	present bool,
) bool {
	switch request.ExpectedPriorBinding.Variant {
	case tenantauthorityv1.BindingExpectedPriorAbsent:
		return !present
	case tenantauthorityv1.BindingExpectedPriorExact:
		// The expected digest is not a substitute for authorizing the locked
		// typed binding as this exact OrgID and namespace.
		return activeBindingDependencyMatches(
			current,
			present,
			request.TargetOrgID,
			request.DesiredBindingValue.TenantNamespaceID,
			request.ExpectedPriorBinding.BindingValueDigest,
		)
	default:
		return false
	}
}

func decideSlot(
	request tenantauthorityv1.TenantLayoutBundleSlotCommandRequest,
	namespace namespaceCurrent,
	namespacePresent bool,
	binding bindingCurrent,
	bindingPresent bool,
	observedPrior ValueReference,
) Result {
	if !activeNamespaceDependencyMatches(
		namespace,
		namespacePresent,
		request.TargetBundleKey.SlotOwnerNamespace,
		request.ExpectedNamespaceReservationDigest,
	) {
		return ResultNamespaceDependencyMismatch
	}
	if !activeBindingDependencyMatches(
		binding,
		bindingPresent,
		request.AuthorizingOrgID,
		request.TargetBundleKey.SlotOwnerNamespace,
		request.ExpectedAuthorizingOrgBindingDigest,
	) {
		return ResultBindingDependencyMismatch
	}
	if observedPrior != slotExpectedReference(request.ExpectedPriorSlot) {
		return ResultExpectedPriorMismatch
	}
	return ResultApplied
}

func activeNamespaceDependencyMatches(
	current namespaceCurrent,
	present bool,
	tenantNamespaceID string,
	expectedDigest string,
) bool {
	// The digest comparison is a CAS identity check, not a substitute for
	// authorizing the locked typed state and namespace.
	return present &&
		current.Value.State == tenantauthorityv1.NamespaceStateActive &&
		current.Value.TenantNamespaceID == tenantNamespaceID &&
		current.Digest == expectedDigest
}

func activeBindingDependencyMatches(
	current bindingCurrent,
	present bool,
	orgID string,
	tenantNamespaceID string,
	expectedDigest string,
) bool {
	// Require the locked binding itself to authorize this exact org/namespace;
	// an opaque digest match alone grants no cross-namespace authority.
	return present &&
		current.Value.State == tenantauthorityv1.OrgBindingStateActive &&
		current.Value.OrgID == orgID &&
		current.Value.TenantNamespaceID == tenantNamespaceID &&
		current.Digest == expectedDigest
}

func namespaceExpectedReference(
	prior tenantauthorityv1.TenantNamespaceExpectedPrior,
) ValueReference {
	if prior.Variant == tenantauthorityv1.NamespaceExpectedPriorExact {
		return ValueReference{Present: true, Digest: prior.NamespaceValueDigest}
	}
	return ValueReference{}
}

func bindingExpectedReference(
	prior tenantauthorityv1.TenantOrgBindingExpectedPrior,
) ValueReference {
	if prior.Variant == tenantauthorityv1.BindingExpectedPriorExact {
		return ValueReference{Present: true, Digest: prior.BindingValueDigest}
	}
	return ValueReference{}
}

func slotExpectedReference(
	prior tenantauthorityv1.TenantSlotExpectedPrior,
) ValueReference {
	if prior.Variant == tenantauthorityv1.SlotExpectedPriorExact {
		return ValueReference{Present: true, Digest: prior.SlotValueDigest}
	}
	return ValueReference{}
}

func validateNamespaceOutcome(
	request tenantauthorityv1.TenantNamespaceCommandRequest,
	command terminalCommand,
) error {
	requestDigest, err := tenantauthorityv1.NamespaceCommandRequestDigest(request)
	if err != nil {
		return err
	}
	if command.Kind != commandKindNamespace || command.RequestDigest != requestDigest {
		return ErrConflict
	}
	desiredDigest, err := tenantauthorityv1.NamespaceValueDigest(request.DesiredNamespaceValue)
	if err != nil {
		return err
	}
	expected := namespaceExpectedReference(request.ExpectedPriorNamespace)
	switch command.Result {
	case ResultApplied:
		if command.ObservedPrior != expected ||
			command.ResultingValue != (ValueReference{Present: true, Digest: desiredDigest}) {
			return fmt.Errorf("%w: applied namespace outcome violates its request", ErrCorrupt)
		}
	case ResultExpectedPriorMismatch:
		if command.ObservedPrior == expected {
			return fmt.Errorf("%w: namespace mismatch outcome observed its expected prior", ErrCorrupt)
		}
	case ResultActiveBinding, ResultActiveSlot:
		if request.Operation != tenantauthorityv1.NamespaceOperationTombstone ||
			command.ObservedPrior != expected {
			return fmt.Errorf("%w: namespace dependency outcome violates its request", ErrCorrupt)
		}
	default:
		return fmt.Errorf("%w: invalid namespace outcome result %q", ErrCorrupt, command.Result)
	}
	return nil
}

func validateBindingOutcome(
	request tenantauthorityv1.TenantOrgNamespaceBindingCommandRequest,
	command terminalCommand,
) error {
	requestDigest, err := tenantauthorityv1.OrgBindingCommandRequestDigest(request)
	if err != nil {
		return err
	}
	if command.Kind != commandKindBinding || command.RequestDigest != requestDigest {
		return ErrConflict
	}
	desiredDigest, err := tenantauthorityv1.OrgBindingValueDigest(request.DesiredBindingValue)
	if err != nil {
		return err
	}
	expectedNamespace := ValueReference{
		Present: true,
		Digest:  request.ExpectedNamespaceReservationDigest,
	}
	expectedPrior := bindingExpectedReference(request.ExpectedPriorBinding)
	switch command.Result {
	case ResultApplied:
		if command.ObservedNamespace != expectedNamespace ||
			command.ObservedPrior != expectedPrior ||
			command.ResultingValue != (ValueReference{Present: true, Digest: desiredDigest}) {
			return fmt.Errorf("%w: applied binding outcome violates its request", ErrCorrupt)
		}
	case ResultNamespaceDependencyMismatch:
		if command.ObservedNamespace == expectedNamespace {
			return fmt.Errorf("%w: namespace mismatch outcome observed its dependency", ErrCorrupt)
		}
	case ResultExpectedPriorMismatch:
		if command.ObservedNamespace != expectedNamespace ||
			command.ObservedPrior == expectedPrior {
			return fmt.Errorf("%w: binding prior mismatch outcome violates its request", ErrCorrupt)
		}
	case ResultActiveSlot:
		if request.Operation != tenantauthorityv1.BindingOperationTombstone ||
			command.ObservedNamespace != expectedNamespace ||
			command.ObservedPrior != expectedPrior {
			return fmt.Errorf("%w: binding slot dependency outcome violates its request", ErrCorrupt)
		}
	default:
		return fmt.Errorf("%w: invalid binding outcome result %q", ErrCorrupt, command.Result)
	}
	return nil
}

func validateSlotOutcome(
	request tenantauthorityv1.TenantLayoutBundleSlotCommandRequest,
	command terminalCommand,
) error {
	requestDigest, err := tenantauthorityv1.SlotCommandRequestDigest(request)
	if err != nil {
		return err
	}
	if command.Kind != commandKindSlot || command.RequestDigest != requestDigest {
		return ErrConflict
	}
	desiredDigest, err := tenantauthorityv1.SlotValueDigest(request.DesiredSlotValue)
	if err != nil {
		return err
	}
	expectedNamespace := ValueReference{
		Present: true,
		Digest:  request.ExpectedNamespaceReservationDigest,
	}
	expectedBinding := ValueReference{
		Present: true,
		Digest:  request.ExpectedAuthorizingOrgBindingDigest,
	}
	expectedPrior := slotExpectedReference(request.ExpectedPriorSlot)
	switch command.Result {
	case ResultApplied:
		if command.ObservedNamespace != expectedNamespace ||
			command.ObservedBinding != expectedBinding ||
			command.ObservedPrior != expectedPrior ||
			command.ResultingValue != (ValueReference{Present: true, Digest: desiredDigest}) {
			return fmt.Errorf("%w: applied slot outcome violates its request", ErrCorrupt)
		}
	case ResultNamespaceDependencyMismatch:
		if command.ObservedNamespace == expectedNamespace {
			return fmt.Errorf("%w: namespace mismatch outcome observed its dependency", ErrCorrupt)
		}
	case ResultBindingDependencyMismatch:
		if command.ObservedNamespace != expectedNamespace ||
			command.ObservedBinding == expectedBinding {
			return fmt.Errorf("%w: binding mismatch outcome violates its request", ErrCorrupt)
		}
	case ResultExpectedPriorMismatch:
		if command.ObservedNamespace != expectedNamespace ||
			command.ObservedBinding != expectedBinding ||
			command.ObservedPrior == expectedPrior {
			return fmt.Errorf("%w: slot prior mismatch outcome violates its request", ErrCorrupt)
		}
	default:
		return fmt.Errorf("%w: invalid slot outcome result %q", ErrCorrupt, command.Result)
	}
	return nil
}
