package connectorbindingownerv1

import (
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
)

const (
	definitionDigestDomain  = "nexuscrm.connector-binding-owner-definition-payload/v1"
	receiptCanonicalDomain  = "nexuscrm.connector-binding-owner-receipt-canonical/v1"
	envelopeCanonicalDomain = "nexuscrm.connector-binding-owner-envelope-canonical/v1"
)

// NewDefinitionEnvelope validates the complete source material and derives
// every digest and receipt field. Neither an empty nor unknown protocol is
// defaulted.
func NewDefinitionEnvelope(input AuthoringInput) (DefinitionEnvelope, error) {
	if err := validateSource(input.Identity, input.CanonicalDefinition); err != nil {
		return DefinitionEnvelope{}, err
	}
	return deriveEnvelope(input.Identity, input.CanonicalDefinition), nil
}

func deriveEnvelope(identity Identity, canonicalDefinition []byte) DefinitionEnvelope {
	definition := cloneBytes(canonicalDefinition)
	definitionDigest := ComputeCanonicalDefinitionDigest(definition)
	receipt := canonicalOwnerReceiptBytes(identity, definitionDigest)
	return DefinitionEnvelope{
		EnvelopeVersion:           EnvelopeVersion,
		ReceiptVersion:            ReceiptVersion,
		Identity:                  identity,
		CanonicalDefinition:       definition,
		CanonicalDefinitionDigest: definitionDigest,
		OwnerReceipt:              receipt,
		OwnerReceiptDigest:        digestPrefixed(receipt),
	}
}

// ComputeCanonicalDefinitionDigest hashes the exact opaque definition bytes
// under the v1 domain. It performs no kind-specific semantic validation.
func ComputeCanonicalDefinitionDigest(canonicalDefinition []byte) string {
	preimage := canonicalPrefix(definitionDigestDomain)
	preimage = appendCanonicalBytes(preimage, canonicalDefinition)
	return digestPrefixed(preimage)
}

// CanonicalOwnerReceiptBytes returns the deterministic compact receipt after
// validating that every stored envelope field rederives exactly.
func CanonicalOwnerReceiptBytes(value DefinitionEnvelope) ([]byte, error) {
	if err := ValidateDefinitionEnvelope(value); err != nil {
		return nil, err
	}
	return cloneBytes(value.OwnerReceipt), nil
}

// CanonicalDefinitionEnvelopeBytes returns the complete v1 binary envelope.
// Strings and byte strings use uint64 big-endian length frames; the definition
// version is one uint64 big-endian value. Exact bytes are never normalized.
func CanonicalDefinitionEnvelopeBytes(value DefinitionEnvelope) ([]byte, error) {
	if err := ValidateDefinitionEnvelope(value); err != nil {
		return nil, err
	}
	result := canonicalPrefix(envelopeCanonicalDomain)
	result = appendCanonicalString(result, value.EnvelopeVersion)
	result = appendCanonicalString(result, value.ReceiptVersion)
	result = appendCanonicalIdentity(result, value.Identity)
	result = appendCanonicalBytes(result, value.CanonicalDefinition)
	result = appendCanonicalString(result, value.CanonicalDefinitionDigest)
	result = appendCanonicalBytes(result, value.OwnerReceipt)
	result = appendCanonicalString(result, value.OwnerReceiptDigest)
	return result, nil
}

// DefinitionEnvelopeDigest content-addresses the complete validated envelope.
func DefinitionEnvelopeDigest(value DefinitionEnvelope) (string, error) {
	canonical, err := CanonicalDefinitionEnvelopeBytes(value)
	if err != nil {
		return "", err
	}
	return digestPrefixed(canonical), nil
}

func canonicalOwnerReceiptBytes(identity Identity, definitionDigest string) []byte {
	result := canonicalPrefix(receiptCanonicalDomain)
	result = appendCanonicalString(result, ReceiptVersion)
	result = appendCanonicalString(result, EnvelopeVersion)
	result = appendCanonicalIdentity(result, identity)
	return appendCanonicalString(result, definitionDigest)
}

func appendCanonicalIdentity(destination []byte, identity Identity) []byte {
	destination = appendCanonicalString(destination, string(identity.Kind))
	destination = appendCanonicalString(destination, identity.OwnerPack)
	destination = appendCanonicalString(destination, identity.PackInstallationID)
	destination = appendCanonicalString(destination, identity.PackPayloadDigest)
	destination = appendCanonicalString(destination, identity.BusinessKey)
	return binary.BigEndian.AppendUint64(destination, identity.DefinitionVersion)
}

func canonicalPrefix(domain string) []byte {
	result := make([]byte, 0, len(domain)+1)
	result = append(result, domain...)
	return append(result, 0)
}

func appendCanonicalString(destination []byte, value string) []byte {
	return appendCanonicalBytes(destination, []byte(value))
}

func appendCanonicalBytes(destination, value []byte) []byte {
	destination = binary.BigEndian.AppendUint64(destination, uint64(len(value)))
	return append(destination, value...)
}

func digestPrefixed(value []byte) string {
	digest := sha256.Sum256(value)
	return "sha256:" + hex.EncodeToString(digest[:])
}
