package catalogpublication

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

const outcomeDigestDomain = "nexuscrm.catalog-publication-outcome/v1"

func canonicalOutcomeDigest(outcome Outcome) (string, error) {
	if err := validateOpaque("OrgID", outcome.OrgID, 255); err != nil {
		return "", err
	}
	if !validCommandID(outcome.CommandID) {
		return "", fmt.Errorf("catalog publication: outcome CommandID must be a lowercase UUIDv7")
	}
	if err := validateSHA256Digest(outcome.CandidatePublicationDigest); err != nil {
		return "", fmt.Errorf("catalog publication: candidate publication digest: %w", err)
	}
	if err := validateReference("ObservedPrior", outcome.ObservedPrior); err != nil {
		return "", err
	}
	if err := validateReference("ResultingActive", outcome.ResultingActive); err != nil {
		return "", err
	}
	if err := validateOutcomeSemantics(outcome); err != nil {
		return "", err
	}

	var canonical bytes.Buffer
	writeCommandString(&canonical, outcomeDigestDomain)
	writeCommandString(&canonical, outcome.OrgID)
	writeCommandString(&canonical, outcome.CommandID)
	writeCommandString(&canonical, outcome.CandidatePublicationDigest)
	writeCommandString(&canonical, string(outcome.Result))
	writeReference(&canonical, outcome.ObservedPrior)
	writeReference(&canonical, outcome.ResultingActive)
	digest := sha256.Sum256(canonical.Bytes())
	return "sha256:" + hex.EncodeToString(digest[:]), nil
}

func validateOutcomeSemantics(outcome Outcome) error {
	switch outcome.Result {
	case ResultSelected:
		if !outcome.ResultingActive.Present || outcome.ResultingActive.Digest != outcome.CandidatePublicationDigest {
			return fmt.Errorf("catalog publication: selected outcome must activate its candidate")
		}
	case ResultUnchanged:
		if !outcome.ObservedPrior.Present ||
			outcome.ObservedPrior.Digest != outcome.CandidatePublicationDigest ||
			outcome.ResultingActive != outcome.ObservedPrior {
			return fmt.Errorf("catalog publication: unchanged outcome must observe and retain its candidate")
		}
	case ResultExpectedPriorMismatch:
		if outcome.ResultingActive != outcome.ObservedPrior {
			return fmt.Errorf("catalog publication: expected-prior mismatch must retain the observed publication")
		}
	default:
		return fmt.Errorf("catalog publication: unknown outcome result %q", outcome.Result)
	}
	return nil
}

func writeReference(out *bytes.Buffer, reference Reference) {
	if !reference.Present {
		out.WriteByte(0)
		return
	}
	out.WriteByte(1)
	writeCommandString(out, reference.Digest)
}
