package runtimeapi

import (
	"context"
	"time"

	"github.com/nexuscrm/metadata/catalogsnapshot"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
)

const integrationConsumerPrepareAttemptCap = 30 * time.Second

// integrationConsumerSelection is the common immutable boundary shared by
// consumer-only Runtime mechanisms. It does not manufacture an L1 handler
// binding: the prepared IntegrationBindingView is captured with the Runtime
// projection for one admitted publication.
type integrationConsumerSelection struct {
	orgID, appInstallationID, workloadID, capability string
	runtimeView                                      *catalogsnapshot.RuntimeMetadataView
}

// prepareIntegrationConsumerAuthority performs the one durable Catalog read
// while preparing an Integration consumer. Publication preparation, rather
// than a request poll, reads and verifies the next immutable authority.
func prepareIntegrationConsumerAuthority(
	ctx context.Context,
	selection integrationConsumerSelection,
	catalogView CatalogConsumerViewEngine,
) (catalogsemantic.IntegrationBindingView, error) {
	if catalogView == nil || selection.runtimeView == nil {
		return catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureUnavailable,
		}
	}
	if ctx == nil {
		return catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureUnavailable,
		}
	}
	catalogScope := CatalogConsumerScope{
		OrgID: selection.orgID, AppInstallationID: selection.appInstallationID,
		WorkloadID:   selection.workloadID,
		Capabilities: []string{catalogsemantic.IntegrationBindingCapabilityName},
	}
	readCtx, cancel := context.WithTimeout(ctx, integrationConsumerPrepareAttemptCap)
	defer cancel()
	current, err := catalogView.GetIntegrationBindingView(
		readCtx, catalogScope, catalogsemantic.GetRequest{},
	)
	if err != nil {
		return catalogsemantic.IntegrationBindingView{}, classifyPlatformEngineError(
			readCtx, err, recordsemantic.InvalidComponentResponse,
		)
	}
	if catalogsemantic.ValidateIntegrationBindingView(current) != nil {
		return catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureInternal,
		}
	}
	if err := validateIntegrationBindingViewScope(catalogScope, nil, current); err != nil {
		return catalogsemantic.IntegrationBindingView{}, err
	}
	lineage := selection.runtimeView.Lineage()
	if !commonsemantic.ValidReceipts(current.Receipts, catalogsemantic.IntegrationBindingCapabilityName) ||
		current.Receipts.CatalogRevision != current.Lineage.CatalogRevision ||
		current.Receipts.SchemaGeneration != current.Lineage.SchemaGeneration ||
		current.Receipts.EngineGeneration != current.Lineage.EngineGeneration ||
		uint64(lineage.CatalogRevision) != current.Lineage.CatalogRevision ||
		uint64(lineage.SchemaGeneration) != current.Lineage.SchemaGeneration ||
		uint64(lineage.EngineGeneration) != current.Lineage.EngineGeneration ||
		string(selection.runtimeView.SourceSnapshotDigest()) != current.Lineage.SourceSnapshotDigest {
		return catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureStaleState, StaleAxis: recordsemantic.StaleAxisCatalog,
		}
	}
	return cloneRuntimeIntegrationBindingView(current), nil
}

func bindIntegrationConsumerAuthority(
	ctx context.Context,
	selection integrationConsumerSelection,
	prepared catalogsemantic.IntegrationBindingView,
) (context.Context, catalogsemantic.IntegrationBindingView, error) {
	if ctx == nil || ctx.Err() != nil {
		if ctx != nil {
			return nil, catalogsemantic.IntegrationBindingView{}, ctx.Err()
		}
		return nil, catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureUnavailable,
		}
	}
	if selection.runtimeView == nil ||
		catalogsemantic.ValidateIntegrationBindingView(prepared) != nil {
		return nil, catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureUnavailable,
		}
	}
	trusted, ok := trustedWorkloadCallFromContext(ctx)
	if !ok {
		return nil, catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureUnavailable,
		}
	}
	if trusted.orgID != selection.orgID ||
		trusted.callerWorkloadID != selection.workloadID ||
		trusted.capability != selection.capability {
		return nil, catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureForbidden,
		}
	}
	if prepared.Receipts.SecurityEpoch != trusted.securityEpoch {
		return nil, catalogsemantic.IntegrationBindingView{}, &recordsemantic.Failure{
			Category: recordsemantic.FailureStaleState, StaleAxis: recordsemantic.StaleAxisSecurity,
			RequiredRevision: trusted.securityEpoch, ServedRevision: prepared.Receipts.SecurityEpoch,
		}
	}
	current := cloneRuntimeIntegrationBindingView(prepared)
	admitted := context.WithValue(ctx, runtimeExecutionScopeContextKey{}, RuntimeExecutionScope{
		view: selection.runtimeView, orgID: selection.orgID,
		appInstallationID: selection.appInstallationID, workloadID: selection.workloadID,
		capability: selection.capability,
		receipts:   receiptsForCapability(current.Receipts, selection.capability),
	})
	return admitted, current, nil
}

func cloneRuntimeIntegrationBindingView(
	view catalogsemantic.IntegrationBindingView,
) catalogsemantic.IntegrationBindingView {
	cloned := view
	cloned.Bindings = make([]catalogsemantic.IntegrationBinding, len(view.Bindings))
	for index := range view.Bindings {
		cloned.Bindings[index] = view.Bindings[index]
		cloned.Bindings[index].CredentialRefs = append(
			[]string(nil), view.Bindings[index].CredentialRefs...,
		)
		if view.Bindings[index].RuntimeCommand != nil {
			command := *view.Bindings[index].RuntimeCommand
			cloned.Bindings[index].RuntimeCommand = &command
		}
	}
	return cloned
}
