package controlplane

import (
	"context"
	"fmt"
	"strings"
)

// ComponentRow is one logical metadata row. Providers use only neutral Go
// values; the control-plane host owns translation to its persistence driver.
type ComponentRow map[string]any

// ComponentRowQuery requests named columns from one logical table, constrained
// by equality predicates. The host must validate every identifier against the
// registered component write surface before constructing a persistence query.
type ComponentRowQuery struct {
	Table   string
	Columns []string
	Equal   ComponentRow
}

// ComponentRowReader is the read-only metadata view supplied to provider
// preflight, verification, and live-key enumeration callbacks.
type ComponentRowReader interface {
	ReadRows(context.Context, ComponentRowQuery) ([]ComponentRow, error)
}

// ComponentInsert describes one metadata-row insertion. Columns listed in
// CurrentTimestampColumns are assigned the host's transaction timestamp and
// therefore must not also appear in Values.
type ComponentInsert struct {
	Table                   string
	Values                  ComponentRow
	CurrentTimestampColumns []string
}

// ComponentUpdate describes a constrained metadata-row update. An empty Match
// is invalid so a provider cannot accidentally update a whole table.
type ComponentUpdate struct {
	Table                   string
	Match                   ComponentRow
	Values                  ComponentRow
	CurrentTimestampColumns []string
}

// ComponentDelete describes a constrained metadata-row deletion. An empty
// Match is invalid so a provider cannot accidentally delete a whole table.
type ComponentDelete struct {
	Table string
	Match ComponentRow
}

// ComponentMutationStore is the transaction-scoped metadata store supplied to
// a managed provider's Apply callback. The host opens and owns the transaction:
// every method, including reads and locks, operates on that same transaction,
// and the host commits only after Apply returns nil. Providers cannot commit or
// roll back independently.
type ComponentMutationStore interface {
	ComponentRowReader

	LockRows(context.Context, ComponentRowQuery) ([]ComponentRow, error)
	NewID() string
	InsertRow(context.Context, ComponentInsert) error
	UpdateRows(context.Context, ComponentUpdate) (int64, error)
	DeleteRows(context.Context, ComponentDelete) (int64, error)
}

// ComponentCollisionClaim names one additional uniqueness surface reserved by
// a managed component beyond its primary manifest business key.
type ComponentCollisionClaim struct {
	Namespace string
	Key       string
}

// ComponentBuildManagedComponent is the descriptor recorded by a build
// handler after it has decoded and validated a source-backed payload.
// ExpectedProjection is the provider's canonical, lossless projection used by
// Apply and VerifyProjection. CollisionClaims are checked before any mutation.
type ComponentBuildManagedComponent struct {
	ExpectedProjection              []byte
	PublishRuntimeStartupProjection bool
	CollisionClaims                 []ComponentCollisionClaim
}

// ComponentManagedRequest carries the immutable source declaration and its
// provider-built expected projection to one managed lifecycle callback.
// SourceOwner is empty for pack-owned component kinds and carries the explicit
// owner discriminator for source-owned component kinds.
type ComponentManagedRequest struct {
	PackName           string
	SourceOwner        string
	Component          ManifestComponent
	Payload            []byte
	ExpectedProjection []byte
}

// ComponentManagedSetRequest identifies the owner-scoped live set enumerated
// during reconciliation of one managed component kind. SourceOwner is empty for
// pack-owned component kinds and set for source-owned component kinds.
type ComponentManagedSetRequest struct {
	PackName    string
	SourceOwner string
	Kind        ComponentKind
}

// ComponentLiveBusinessKey is one live component identity. Customized tells
// the generic reconciler whether an undeclared row is an admin-owned overlay
// that must be preserved rather than removed.
type ComponentLiveBusinessKey struct {
	BusinessKey string
	Customized  bool
}

// ComponentManagedProvider owns the component-specific persistence projection
// while the control-plane host owns lifecycle ordering and transactions.
//
// Preflight must detect collisions without mutation. Apply receives the only
// mutation capability and runs inside a host-owned transaction.
// VerifyProjection returns canonical expected and live bytes for exact drift
// comparison. ListLiveBusinessKeys supplies the owner-scoped live set used by
// generic reconciliation. ReconcileSet runs inside a host-owned transaction;
// the host supplies a sorted, unique snapshot of declared business keys, while
// the provider owns stale-row and customization semantics, including the empty
// snapshot used when every component of the kind was dropped.
//
// Deprecated: use ComponentManagedLifecycleRegistration and the Plan/Mutate/
// Project lifecycle. This five-callback shape remains available for source
// compatibility during the additive migration.
type ComponentManagedProvider struct {
	Preflight            func(context.Context, ComponentRowReader, ComponentManagedRequest) error
	Apply                func(context.Context, ComponentMutationStore, ComponentManagedRequest) error
	VerifyProjection     func(context.Context, ComponentRowReader, ComponentManagedRequest) (normalizedExpected, live []byte, err error)
	ListLiveBusinessKeys func(context.Context, ComponentRowReader, ComponentManagedSetRequest) ([]ComponentLiveBusinessKey, error)
	ReconcileSet         func(context.Context, ComponentMutationStore, ComponentManagedSetRequest, []string) error
}

// Validate rejects an unbounded or ambiguous read request. Identifier allowlist
// enforcement remains the host's responsibility because only it owns the
// registered component write surface.
func (q ComponentRowQuery) Validate() error {
	if err := validateComponentTable(q.Table); err != nil {
		return err
	}
	if err := validateComponentColumns("columns", q.Columns); err != nil {
		return err
	}
	if err := validateComponentRow("equal predicates", q.Equal, true); err != nil {
		return err
	}
	return nil
}

// Validate rejects an insertion with no values or ambiguous timestamp columns.
func (i ComponentInsert) Validate() error {
	if err := validateComponentTable(i.Table); err != nil {
		return err
	}
	if err := validateComponentRow("values", i.Values, true); err != nil {
		return err
	}
	if err := validateComponentColumnsOptional("current timestamp columns", i.CurrentTimestampColumns); err != nil {
		return err
	}
	return rejectComponentColumnOverlap(i.Values, i.CurrentTimestampColumns)
}

// Validate rejects an unbounded update, an empty mutation, or ambiguous
// timestamp columns.
func (u ComponentUpdate) Validate() error {
	if err := validateComponentTable(u.Table); err != nil {
		return err
	}
	if err := validateComponentRow("match predicates", u.Match, true); err != nil {
		return err
	}
	if err := validateComponentRow("values", u.Values, false); err != nil {
		return err
	}
	if err := validateComponentColumnsOptional("current timestamp columns", u.CurrentTimestampColumns); err != nil {
		return err
	}
	if len(u.Values) == 0 && len(u.CurrentTimestampColumns) == 0 {
		return fmt.Errorf("update requires values or current timestamp columns")
	}
	return rejectComponentColumnOverlap(u.Values, u.CurrentTimestampColumns)
}

// Validate rejects a deletion without a limiting predicate.
func (d ComponentDelete) Validate() error {
	if err := validateComponentTable(d.Table); err != nil {
		return err
	}
	return validateComponentRow("match predicates", d.Match, true)
}

// Validate rejects an incomplete collision claim.
func (c ComponentCollisionClaim) Validate() error {
	if strings.TrimSpace(c.Namespace) == "" {
		return fmt.Errorf("collision claim namespace is required")
	}
	if strings.TrimSpace(c.Key) == "" {
		return fmt.Errorf("collision claim key is required")
	}
	return nil
}

// Validate rejects an empty expected projection or ambiguous duplicate
// collision claims.
func (c ComponentBuildManagedComponent) Validate() error {
	if len(c.ExpectedProjection) == 0 {
		return fmt.Errorf("managed component expected projection is required")
	}
	seen := make(map[string]struct{}, len(c.CollisionClaims))
	for index, claim := range c.CollisionClaims {
		if err := claim.Validate(); err != nil {
			return fmt.Errorf("collision claim %d: %w", index, err)
		}
		identity := strings.TrimSpace(claim.Namespace) + "\x00" + strings.TrimSpace(claim.Key)
		if _, exists := seen[identity]; exists {
			return fmt.Errorf("collision claim %d duplicates namespace %q key %q", index, strings.TrimSpace(claim.Namespace), strings.TrimSpace(claim.Key))
		}
		seen[identity] = struct{}{}
	}
	return nil
}

// Validate rejects an incomplete managed callback set. A registration that
// opts into ManagedProvider must supply all five lifecycle operations; the host
// must not fall back to an internal component-specific path.
func (p *ComponentManagedProvider) Validate() error {
	if p == nil {
		return fmt.Errorf("managed component provider is required")
	}
	missing := make([]string, 0, 5)
	if p.Preflight == nil {
		missing = append(missing, "Preflight")
	}
	if p.Apply == nil {
		missing = append(missing, "Apply")
	}
	if p.VerifyProjection == nil {
		missing = append(missing, "VerifyProjection")
	}
	if p.ListLiveBusinessKeys == nil {
		missing = append(missing, "ListLiveBusinessKeys")
	}
	if p.ReconcileSet == nil {
		missing = append(missing, "ReconcileSet")
	}
	if len(missing) > 0 {
		return fmt.Errorf("managed component provider is missing callbacks: %s", strings.Join(missing, ", "))
	}
	return nil
}

func validateComponentTable(table string) error {
	if strings.TrimSpace(table) == "" {
		return fmt.Errorf("table is required")
	}
	return nil
}

func validateComponentRow(label string, row ComponentRow, required bool) error {
	if required && len(row) == 0 {
		return fmt.Errorf("%s are required", label)
	}
	for column := range row {
		if strings.TrimSpace(column) == "" {
			return fmt.Errorf("%s contain a blank column", label)
		}
	}
	return nil
}

func validateComponentColumns(label string, columns []string) error {
	if len(columns) == 0 {
		return fmt.Errorf("%s are required", label)
	}
	return validateComponentColumnsOptional(label, columns)
}

func validateComponentColumnsOptional(label string, columns []string) error {
	seen := make(map[string]struct{}, len(columns))
	for index, column := range columns {
		trimmed := strings.TrimSpace(column)
		if trimmed == "" {
			return fmt.Errorf("%s contain a blank column at index %d", label, index)
		}
		if _, exists := seen[trimmed]; exists {
			return fmt.Errorf("%s contain duplicate column %q", label, trimmed)
		}
		seen[trimmed] = struct{}{}
	}
	return nil
}

func rejectComponentColumnOverlap(values ComponentRow, timestampColumns []string) error {
	for _, column := range timestampColumns {
		if _, exists := values[strings.TrimSpace(column)]; exists {
			return fmt.Errorf("column %q cannot be both a value and a current timestamp column", strings.TrimSpace(column))
		}
	}
	return nil
}
