package rewindplan

import (
	"fmt"
	"strings"
)

// OperationStatus is the durable rewind operation lifecycle stored in
// rewind_operations.status. The database column keeps its historical name, but
// the status set is owned here so API/store code does not invent ad-hoc states.
type OperationStatus string

const (
	OperationStatusPreparing      OperationStatus = "preparing"
	OperationStatusCompleted      OperationStatus = "completed"
	OperationStatusFailed         OperationStatus = "failed"
	OperationStatusRepairRequired OperationStatus = "repair_required"
)

func (status OperationStatus) String() string {
	return string(status)
}

func (status OperationStatus) Valid() bool {
	switch status {
	case OperationStatusPreparing, OperationStatusCompleted, OperationStatusFailed, OperationStatusRepairRequired:
		return true
	default:
		return false
	}
}

// ActiveRewindOperationStatuses are statuses that reserve a rewind scope and
// block another concurrent rewind from starting on the same primary scope.
func ActiveRewindOperationStatuses() []OperationStatus {
	return []OperationStatus{OperationStatusPreparing}
}

// SuffixBoundaryRole describes why a scope suffix is being rewound. Rewind
// always tombstones explicit scope-local suffixes: one primary scope plus optional
// reply-thread cascade scopes hanging off suffix root messages.
type SuffixBoundaryRole string

const (
	SuffixBoundaryPrimary            SuffixBoundaryRole = "primary"
	SuffixBoundaryCascadeReplyThread SuffixBoundaryRole = "cascade_reply_thread"
)

// SuffixBoundary is the canonical plan-time contract for the scope-local suffix
// that rewind is allowed to tombstone. Parent/broadcast scopes may be used for
// realtime or authorization, but they are not tombstone boundaries unless they
// appear here explicitly.
type SuffixBoundary struct {
	Role            SuffixBoundaryRole
	ScopeID         string
	TargetMessageID string
	FromSeq         int64
	MessageIDs      []string
	ParentMessageID string
}

func (boundary SuffixBoundary) Normalize() SuffixBoundary {
	boundary.ScopeID = strings.TrimSpace(boundary.ScopeID)
	boundary.TargetMessageID = strings.TrimSpace(boundary.TargetMessageID)
	boundary.ParentMessageID = strings.TrimSpace(boundary.ParentMessageID)
	boundary.MessageIDs = NormalizeNonEmptyStrings(boundary.MessageIDs)
	return boundary
}

func (boundary SuffixBoundary) Validate() error {
	boundary = boundary.Normalize()
	if !boundary.Role.valid() {
		return fmt.Errorf("rewind suffix boundary role %q is not supported", boundary.Role)
	}
	if boundary.ScopeID == "" {
		return fmt.Errorf("rewind suffix boundary scope id is required")
	}
	if boundary.Role == SuffixBoundaryPrimary {
		if len(boundary.MessageIDs) == 0 {
			return fmt.Errorf("rewind suffix boundary message ids are required")
		}
		if boundary.TargetMessageID == "" {
			return fmt.Errorf("rewind primary suffix boundary target message id is required")
		}
		if boundary.FromSeq <= 0 {
			return fmt.Errorf("rewind primary suffix boundary from seq must be positive")
		}
		if strings.TrimSpace(boundary.MessageIDs[0]) != boundary.TargetMessageID {
			return fmt.Errorf("rewind primary suffix boundary must start at target message")
		}
	}
	if boundary.Role == SuffixBoundaryCascadeReplyThread {
		if boundary.ParentMessageID == "" {
			return fmt.Errorf("rewind cascade suffix boundary parent message id is required")
		}
		if boundary.FromSeq != 0 && boundary.FromSeq != 1 {
			return fmt.Errorf("rewind cascade suffix boundary must cover the whole reply thread")
		}
	}
	return nil
}

func (role SuffixBoundaryRole) valid() bool {
	switch role {
	case SuffixBoundaryPrimary, SuffixBoundaryCascadeReplyThread:
		return true
	default:
		return false
	}
}

func firstNonEmptyTrimmed(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

// RewindScopeID returns the primary storage/deletion scope for a rewind plan.
func (plan Plan) RewindScopeID() string {
	return firstNonEmptyTrimmed(plan.ScopeID)
}

// OperationScopeID returns the scope that serializes durable rewind operations.
// The database column is still named rewind_operations.thread_id for historical
// compatibility, but code should treat it as this explicit operation scope.
func (plan Plan) OperationScopeID() string {
	return plan.RewindScopeID()
}

// RealtimeBroadcastScopeID returns the scope that should receive the coarse
// conversation:rewound invalidation after a successful apply.
func (plan Plan) RealtimeBroadcastScopeID() string {
	return firstNonEmptyTrimmed(plan.BroadcastScopeID, plan.RewindScopeID())
}

// ReplyThreadStateScopeID returns the reply-thread scope whose participant and
// membership facts need recompute when the primary rewind target is a reply.
func (plan Plan) ReplyThreadStateScopeID() string {
	return firstNonEmptyTrimmed(plan.ReplyThreadScopeID)
}

// ReplyThreadParentMessageID returns the parent/root message id for a
// reply-thread rewind. It is metadata only, not a deletion boundary by itself.
func (plan Plan) ReplyThreadParentMessageID() string {
	return firstNonEmptyTrimmed(plan.ReplyParentMessageID)
}

func (plan Plan) PrimarySuffixBoundary() SuffixBoundary {
	return SuffixBoundary{
		Role:            SuffixBoundaryPrimary,
		ScopeID:         plan.RewindScopeID(),
		TargetMessageID: strings.TrimSpace(plan.TargetMessageID),
		FromSeq:         plan.TargetSeq,
		MessageIDs:      append([]string(nil), plan.SuffixMessageIDs...),
	}
}

// CascadeScopeID returns the child reply-thread scope that a cascade boundary deletes.
func (thread CascadeReplyThread) CascadeScopeID() string {
	return firstNonEmptyTrimmed(thread.ScopeID)
}

func (thread CascadeReplyThread) SuffixBoundary() SuffixBoundary {
	return SuffixBoundary{
		Role:            SuffixBoundaryCascadeReplyThread,
		ScopeID:         thread.CascadeScopeID(),
		ParentMessageID: strings.TrimSpace(thread.ParentMessageID),
		FromSeq:         1,
		MessageIDs:      append([]string(nil), thread.MessageIDs...),
	}
}

func (plan Plan) SuffixBoundaries() []SuffixBoundary {
	boundaries := []SuffixBoundary{plan.PrimarySuffixBoundary()}
	for _, thread := range plan.CascadeReplyThreads {
		boundaries = append(boundaries, thread.SuffixBoundary())
	}
	return boundaries
}

func (plan Plan) RewindScopeIDs() []string {
	ids := make([]string, 0, 1+len(plan.CascadeReplyThreads))
	for _, boundary := range plan.SuffixBoundaries() {
		ids = append(ids, boundary.ScopeID)
	}
	return NormalizeNonEmptyStrings(ids)
}

func (plan Plan) ValidateSuffixBoundaries() error {
	for _, boundary := range plan.SuffixBoundaries() {
		if err := boundary.Validate(); err != nil {
			return err
		}
	}
	return nil
}
