package catalogpublication

import (
	"fmt"

	catalogpublicationv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalogpublicationv1"
)

// validateSelectionTransition checks the lineage fence for selecting candidate.
// The database authority supplies the scalar writer state that it locked and
// the optional active publication read in the same transaction.
func validateSelectionTransition(
	writerCatalogRevision uint64,
	writerSecurityEpoch uint64,
	active *catalogpublicationv1.Binding,
	candidate catalogpublicationv1.Binding,
) error {
	if err := catalogpublicationv1.ValidateBinding(candidate); err != nil {
		return fmt.Errorf("candidate binding: %w", err)
	}

	if active == nil {
		if writerCatalogRevision != 0 || writerSecurityEpoch != 0 {
			return fmt.Errorf(
				"initial publication requires zero writer state, got CatalogRevision=%d SecurityEpoch=%d",
				writerCatalogRevision,
				writerSecurityEpoch,
			)
		}
		if candidate.CatalogRevision != 1 || candidate.SecurityEpoch != 1 {
			return fmt.Errorf(
				"initial publication must advance both axes from zero to one, got CatalogRevision=%d SecurityEpoch=%d",
				candidate.CatalogRevision,
				candidate.SecurityEpoch,
			)
		}
		return nil
	}

	if err := catalogpublicationv1.ValidateBinding(*active); err != nil {
		return fmt.Errorf("active binding: %w", err)
	}
	if writerCatalogRevision != active.CatalogRevision || writerSecurityEpoch != active.SecurityEpoch {
		return fmt.Errorf(
			"writer state does not match active binding: writer CatalogRevision=%d SecurityEpoch=%d, active CatalogRevision=%d SecurityEpoch=%d",
			writerCatalogRevision,
			writerSecurityEpoch,
			active.CatalogRevision,
			active.SecurityEpoch,
		)
	}
	if candidate.OrgID != active.OrgID {
		return fmt.Errorf("candidate OrgID %q does not match active OrgID %q", candidate.OrgID, active.OrgID)
	}
	return validateBindingStep(*active, candidate)
}

// validateStoppedWriterAdoption checks the deliberately narrower stopped-
// deployment path. Catalog reconcile has already committed the writer axes, so
// the candidate must name that exact current state. The active publication may
// be absent (first adoption), equal to the candidate (exact replay), or exactly
// one lineage step behind it. This path must never be used while serving.
func validateStoppedWriterAdoption(
	writerCatalogRevision uint64,
	writerSecurityEpoch uint64,
	active *catalogpublicationv1.Binding,
	candidate catalogpublicationv1.Binding,
) error {
	if err := catalogpublicationv1.ValidateBinding(candidate); err != nil {
		return fmt.Errorf("candidate binding: %w", err)
	}
	if candidate.CatalogRevision != writerCatalogRevision ||
		candidate.SecurityEpoch != writerSecurityEpoch {
		return fmt.Errorf(
			"candidate does not match current writer state: writer CatalogRevision=%d SecurityEpoch=%d, candidate CatalogRevision=%d SecurityEpoch=%d",
			writerCatalogRevision,
			writerSecurityEpoch,
			candidate.CatalogRevision,
			candidate.SecurityEpoch,
		)
	}
	if active == nil {
		return nil
	}
	if err := catalogpublicationv1.ValidateBinding(*active); err != nil {
		return fmt.Errorf("active binding: %w", err)
	}
	if *active == candidate {
		return nil
	}
	if candidate.OrgID != active.OrgID {
		return fmt.Errorf("candidate OrgID %q does not match active OrgID %q", candidate.OrgID, active.OrgID)
	}
	return validateBindingStep(*active, candidate)
}

func validateBindingStep(
	active catalogpublicationv1.Binding,
	candidate catalogpublicationv1.Binding,
) error {

	catalogAdvanced, err := validateAxisStep("CatalogRevision", active.CatalogRevision, candidate.CatalogRevision)
	if err != nil {
		return err
	}
	securityAdvanced, err := validateAxisStep("SecurityEpoch", active.SecurityEpoch, candidate.SecurityEpoch)
	if err != nil {
		return err
	}
	if !catalogAdvanced && !securityAdvanced {
		return fmt.Errorf("publication must advance CatalogRevision or SecurityEpoch")
	}

	if !catalogAdvanced {
		if candidate.CatalogSnapshotDigest != active.CatalogSnapshotDigest ||
			candidate.SchemaGeneration != active.SchemaGeneration ||
			candidate.EngineGeneration != active.EngineGeneration ||
			candidate.LayoutValidationViewDigest != active.LayoutValidationViewDigest {
			return fmt.Errorf("retained CatalogRevision must retain its catalog binding")
		}
	}
	if !securityAdvanced && candidate.LayoutRoutingSecurityViewDigest != active.LayoutRoutingSecurityViewDigest {
		return fmt.Errorf("retained SecurityEpoch must retain its routing-security binding")
	}
	if !catalogAdvanced && securityAdvanced && candidate.LayoutActivationSetDigest == active.LayoutActivationSetDigest {
		return fmt.Errorf("security-only publication must select a new LayoutActivationSetDigest")
	}

	return nil
}

func validateAxisStep(name string, active, candidate uint64) (bool, error) {
	if candidate == active {
		return false, nil
	}
	if candidate < active {
		return false, fmt.Errorf("%s regressed from %d to %d", name, active, candidate)
	}
	if candidate-active != 1 {
		return false, fmt.Errorf("%s jumped from %d to %d", name, active, candidate)
	}
	return true, nil
}
