package catalogreconcile

import (
	"fmt"

	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/approvalir"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalv1"
)

// BindApprovalPublication opens one L1-compiled neutral bundle before Catalog
// begins an operation. The exact bundle digest and L2 metadata source digest
// must both match the stopped deployment selection; no repository or live-row
// fallback is available when either identity disagrees.
func (c Config) BindApprovalPublication(request Request, raw []byte) (Request, error) {
	if c.ApprovalPublication == nil {
		if len(raw) != 0 {
			return Request{}, fmt.Errorf("Catalog reconcile config does not select an Approval publication bundle")
		}
		return request, nil
	}
	bundle, digest, err := approvalir.DecodePublicationBundle(raw, nil)
	if err != nil {
		return Request{}, fmt.Errorf("open Approval publication bundle: %w", err)
	}
	if digest != c.ApprovalPublication.ExpectedDigest {
		return Request{}, fmt.Errorf(
			"Approval publication bundle digest %q does not match expected %q",
			digest,
			c.ApprovalPublication.ExpectedDigest,
		)
	}
	if bundle.SourceDigest != c.MetadataSourceDigest ||
		bundle.SourceDigest != string(request.MetadataSourceDigest) {
		return Request{}, fmt.Errorf(
			"Approval publication metadata source digest %q does not match Catalog selection %q",
			bundle.SourceDigest,
			c.MetadataSourceDigest,
		)
	}
	if !requestIncludesRuntimeCapability(request, approvalv1.ExecuteCapabilityName) &&
		!requestIncludesRuntimeCapability(request, approvalv1.ReadCapabilityName) {
		return Request{}, fmt.Errorf("Approval publication bundle requires an Approval Runtime capability")
	}
	for _, published := range bundle.Definitions {
		if err := validatePublishedApprovalActions(published.Envelope.Definition); err != nil {
			return Request{}, fmt.Errorf("Approval publication definition %q: %w", published.DefinitionID, err)
		}
	}
	request.approvalPublication = &approvalPublicationSelection{
		bundle: bundle,
		digest: catalogsnapshot.Digest(digest),
	}
	return request, nil
}

// approvalPublicationIdentity closes the sidecar publication input before an
// operation can begin. Re-encoding proves that the decoded content and the
// identity carried into the idempotency journal are still one immutable fact.
func approvalPublicationIdentity(request Request) (catalogsnapshot.Digest, error) {
	selection := request.approvalPublication
	if selection == nil {
		return "", nil
	}
	if selection.bundle == nil {
		return "", fmt.Errorf("Approval publication selection has no decoded bundle")
	}
	if selection.bundle.SourceDigest != string(request.MetadataSourceDigest) {
		return "", fmt.Errorf(
			"Approval publication metadata source digest %q does not match Catalog selection %q",
			selection.bundle.SourceDigest,
			request.MetadataSourceDigest,
		)
	}
	_, digest, err := approvalir.EncodePublicationBundle(*selection.bundle, nil)
	if err != nil {
		return "", fmt.Errorf("revalidate Approval publication bundle: %w", err)
	}
	if catalogsnapshot.Digest(digest) != selection.digest {
		return "", fmt.Errorf("Approval publication content and immutable digest disagree")
	}
	return selection.digest, nil
}

func validatePublishedApprovalActions(definition approvalir.Definition) error {
	groups := [][]approvalir.Action{
		definition.InitialSubmissionActions,
		definition.StepApprovalActions,
		definition.FinalApprovalActions,
		definition.FinalRejectionActions,
		definition.RecallActions,
		definition.ReassignActions,
	}
	for _, step := range definition.Steps {
		groups = append(groups, step.ApprovalActions, step.RejectionActions)
	}
	for _, actions := range groups {
		for _, action := range actions {
			if action.Reference != nil {
				return fmt.Errorf("unresolved action reference %q cannot enter a serving view", action.Reference.APIName)
			}
		}
	}
	return nil
}

func requestIncludesRuntimeCapability(request Request, capabilityID string) bool {
	if request.RuntimeCapabilityID == capabilityID {
		return true
	}
	for _, capability := range request.Capabilities {
		if capability.RuntimeCapabilityID == capabilityID {
			return true
		}
	}
	return false
}
