package serving

import (
	"fmt"
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"

	catalogv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
)

var (
	rawSHA256Pattern      = regexp.MustCompile(`^[0-9a-f]{64}$`)
	prefixedSHA256Pattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
)

// NewL2Snapshot freezes one legacy-unprofiled L2 generation and defensively
// copies its opaque verification receipt. L0 validates only its closed identity
// shape; Profile V2 instead preserves the ServiceConfiguration owner's receipt
// digest exactly.
func NewL2Snapshot(
	configurationGenerationRef string,
	configurationDigest string,
	verificationReceipt []byte,
) (L2Snapshot, error) {
	if err := validateL2SnapshotShape(
		configurationGenerationRef,
		configurationDigest,
		len(verificationReceipt),
	); err != nil {
		return L2Snapshot{}, invalidCandidate("L2 snapshot: %v", err)
	}
	snapshot := L2Snapshot{
		configurationGenerationRef: configurationGenerationRef,
		configurationDigest:        configurationDigest,
		verificationReceipt:        append([]byte(nil), verificationReceipt...),
	}
	snapshot.verificationReceiptDigest = computeL2VerificationReceiptDigest(snapshot.verificationReceipt)
	return snapshot, nil
}

// NewPreparedEpoch derives one legacy-unprofiled inert candidate from a
// contract-valid Catalog view and one closed L2 snapshot. Profile V2 candidates
// use NewProfiledPreparedEpoch. This function performs no external verification,
// persistence, state transition or activation. Catalog validation and digest
// computation intentionally remain delegated to consumerv1 v0.2.21 rather than
// being reimplemented here.
func NewPreparedEpoch(
	epochID string,
	view catalogv1.IntegrationBindingView,
	l2 L2Snapshot,
) (PreparedEpoch, error) {
	if !validOpaqueIdentifier(epochID) {
		return PreparedEpoch{}, invalidCandidate("epoch ID is not canonical")
	}
	if err := catalogv1.ValidateIntegrationBindingView(view); err != nil {
		return PreparedEpoch{}, invalidCandidate("Catalog IntegrationBindingView: %v", err)
	}
	viewDigest := catalogv1.ComputeIntegrationBindingViewDigest(view)
	if view.Lineage.ViewDigest != viewDigest {
		return PreparedEpoch{}, invalidCandidate("Catalog IntegrationBindingView digest disagrees")
	}
	if err := validateL2Snapshot(l2); err != nil {
		return PreparedEpoch{}, invalidCandidate("L2 snapshot: %v", err)
	}

	catalog := catalogReferenceFromView(view)
	l2 = l2.copy()
	bindings, err := buildStoredBindings(epochID, view.Bindings)
	if err != nil {
		return PreparedEpoch{}, invalidCandidate("Catalog bindings: %v", err)
	}
	epoch := PreparedEpoch{
		id:                 epochID,
		servingScopeDigest: computeServingScopeDigest(catalog),
		state:              preparedState,
		catalog:            catalog,
		l2:                 l2,
		bindings:           bindings,
	}
	epoch.epochDigest = computeEpochDigest(epoch)
	return epoch, nil
}

// validateStored re-derives every persisted field from immutable references. A
// Catalog view exists only as a transient validation adapter; it is never kept
// on PreparedEpoch or exposed as an authoritative definition read path.
func (epoch PreparedEpoch) validateStored() error {
	if epoch.state != preparedState || epoch.activeScopeDigest != nil {
		return storedCandidateInvalid("candidate state is not prepared-only")
	}
	if !prefixedSHA256Pattern.MatchString(epoch.epochDigest) ||
		!rawSHA256Pattern.MatchString(epoch.servingScopeDigest) {
		return storedCandidateInvalid("epoch or serving-scope digest shape is invalid")
	}
	for i := range epoch.bindings {
		if !rawSHA256Pattern.MatchString(epoch.bindings[i].id) ||
			!rawSHA256Pattern.MatchString(epoch.bindings[i].identityDigest) {
			return storedCandidateInvalid("binding %d digest shape is invalid", i)
		}
	}
	if err := validateL2Snapshot(epoch.l2); err != nil {
		return storedCandidateInvalid("L2 snapshot: %v", err)
	}
	expected, err := rederiveStoredCandidate(epoch)
	if err != nil {
		return storedCandidateInvalid("cannot derive candidate: %v", err)
	}
	if !epoch.equal(expected) {
		return storedCandidateInvalid("persisted fields disagree with canonical candidate")
	}
	return nil
}

func catalogReferenceFromView(view catalogv1.IntegrationBindingView) catalogReference {
	return catalogReference{
		orgID:                view.OrgID,
		appInstallationID:    view.AppInstallationID,
		authorizedWorkloadID: view.AuthorizedWorkloadID,
		lineage:              view.Lineage,
		receipts:             view.Receipts,
	}
}

// rederiveStoredCandidate constructs a Catalog view only inside this validation
// adapter, delegates closed-shape validation and digest derivation to Catalog's
// contract, and immediately discards it. Serving and future verification must
// resolve definitions from Catalog itself.
func rederiveStoredCandidate(epoch PreparedEpoch) (PreparedEpoch, error) {
	view := catalogv1.IntegrationBindingView{
		OrgID:                epoch.catalog.orgID,
		AppInstallationID:    epoch.catalog.appInstallationID,
		AuthorizedWorkloadID: epoch.catalog.authorizedWorkloadID,
		Lineage:              epoch.catalog.lineage,
		Bindings:             make([]catalogv1.IntegrationBinding, len(epoch.bindings)),
		Receipts:             epoch.catalog.receipts,
	}
	for i := range epoch.bindings {
		binding, err := catalogBindingForValidation(epoch.bindings[i])
		if err != nil {
			return PreparedEpoch{}, fmt.Errorf("catalog reference set binding %d: %w", i, err)
		}
		view.Bindings[i] = binding
	}
	return NewPreparedEpoch(epoch.id, view, epoch.l2)
}

func catalogBindingForValidation(binding storedBinding) (catalogv1.IntegrationBinding, error) {
	credentialRefs, err := decodeCredentialRefs(binding.credentialRefs)
	if err != nil {
		return catalogv1.IntegrationBinding{}, fmt.Errorf("credential refs: %w", err)
	}
	result := catalogv1.IntegrationBinding{
		Kind:               binding.kind,
		OwnerPack:          binding.ownerPack,
		PackInstallationID: binding.packInstallationID,
		PackPayloadDigest:  binding.packPayloadDigest,
		BusinessKey:        binding.businessKey,
		DefinitionVersion:  binding.definitionVersion,
		CredentialRefs:     credentialRefs,
	}
	runtimeFieldCount := 0
	if binding.runtimeCommandKind != nil {
		runtimeFieldCount++
	}
	if binding.runtimePayloadFormatVersion != nil {
		runtimeFieldCount++
	}
	if binding.runtimeDefinitionRef != nil {
		runtimeFieldCount++
	}
	if binding.runtimeDefinitionVersion != nil {
		runtimeFieldCount++
	}
	if runtimeFieldCount != 0 && runtimeFieldCount != 4 {
		return catalogv1.IntegrationBinding{}, fmt.Errorf("runtime reference is partial")
	}
	if runtimeFieldCount == 4 {
		result.RuntimeCommand = &catalogv1.RuntimeCommandBinding{
			CommandKind:          *binding.runtimeCommandKind,
			PayloadFormatVersion: *binding.runtimePayloadFormatVersion,
			DefinitionRef:        *binding.runtimeDefinitionRef,
			DefinitionVersion:    *binding.runtimeDefinitionVersion,
		}
	}
	return result, nil
}

func buildStoredBindings(
	epochID string,
	bindings []catalogv1.IntegrationBinding,
) ([]storedBinding, error) {
	result := make([]storedBinding, len(bindings))
	for i := range bindings {
		binding := bindings[i]
		credentialRefs, err := encodeCredentialRefs(binding.CredentialRefs)
		if err != nil {
			return nil, fmt.Errorf("binding %d credential refs: %w", i, err)
		}
		identityDigest := computeBindingIdentityDigest(binding)
		stored := storedBinding{
			servingEpochID:     epochID,
			ordinal:            uint64(i),
			identityDigest:     identityDigest,
			kind:               binding.Kind,
			ownerPack:          binding.OwnerPack,
			packInstallationID: binding.PackInstallationID,
			packPayloadDigest:  binding.PackPayloadDigest,
			businessKey:        binding.BusinessKey,
			definitionVersion:  binding.DefinitionVersion,
			credentialRefs:     credentialRefs,
		}
		stored.id = computeBindingRowID(epochID, stored.ordinal, identityDigest)
		if binding.RuntimeCommand != nil {
			stored.runtimeCommandKind = stringPointer(binding.RuntimeCommand.CommandKind)
			stored.runtimePayloadFormatVersion = uint32Pointer(binding.RuntimeCommand.PayloadFormatVersion)
			stored.runtimeDefinitionRef = stringPointer(binding.RuntimeCommand.DefinitionRef)
			stored.runtimeDefinitionVersion = stringPointer(binding.RuntimeCommand.DefinitionVersion)
		}
		result[i] = stored
	}
	return result, nil
}

func validateL2Snapshot(snapshot L2Snapshot) error {
	if err := validateL2SnapshotShape(
		snapshot.configurationGenerationRef,
		snapshot.configurationDigest,
		len(snapshot.verificationReceipt),
	); err != nil {
		return err
	}
	if !prefixedSHA256Pattern.MatchString(snapshot.verificationReceiptDigest) ||
		snapshot.verificationReceiptDigest != computeL2VerificationReceiptDigest(snapshot.verificationReceipt) {
		return fmt.Errorf("verification receipt digest disagrees")
	}
	return nil
}

// validateL2SnapshotShape is the allocation-free admission preflight shared by
// public construction and stored-value validation. In particular, callers
// cannot make NewL2Snapshot copy or hash a receipt outside Connector's
// explicitly bounded persisted shape.
func validateL2SnapshotShape(
	configurationGenerationRef string,
	configurationDigest string,
	verificationReceiptBytes int,
) error {
	if !validOpaqueIdentifier(configurationGenerationRef) {
		return fmt.Errorf("configuration generation ref is not canonical")
	}
	if !prefixedSHA256Pattern.MatchString(configurationDigest) {
		return fmt.Errorf("configuration digest is not canonical sha256")
	}
	if verificationReceiptBytes <= 0 {
		return fmt.Errorf("verification receipt is empty")
	}
	if verificationReceiptBytes > maxPersistedL2VerificationReceiptBytes {
		return fmt.Errorf(
			"verification receipt exceeds connector-owned persisted limit of %d bytes",
			maxPersistedL2VerificationReceiptBytes,
		)
	}
	return nil
}

func validOpaqueIdentifier(value string) bool {
	return validOpaqueIdentifierWithin(value, connectorDigestProfileV1MaxIdentifierBytes)
}

func validOpaqueIdentifierWithin(value string, maxBytes int) bool {
	if value == "" || len(value) > maxBytes ||
		!utf8.ValidString(value) || strings.TrimSpace(value) != value ||
		strings.IndexByte(value, 0) >= 0 {
		return false
	}
	for _, character := range value {
		if unicode.IsControl(character) {
			return false
		}
	}
	return true
}

func invalidCandidate(format string, args ...any) error {
	return fmt.Errorf("%w: %s", ErrInvalidCandidate, fmt.Sprintf(format, args...))
}

func storedCandidateInvalid(format string, args ...any) error {
	return fmt.Errorf("%w: %s", ErrStoredCandidateInvalid, fmt.Sprintf(format, args...))
}

func stringPointer(value string) *string { return &value }

func uint32Pointer(value uint32) *uint32 { return &value }
