package agent

import (
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
)

// turnDeliveryState is the per-turn set of conversation targets the runtime
// actually received in the current authoritative request. It mirrors the
// trusted delivery-evidence lifecycle of sendScopeState (commit-point writes,
// owner-scoped handover transactions, generation fencing, turn-end reset) but
// stays an independent state machine: task-creation defaults consume it, not
// the message-send authorization. A turn with exactly one normalizable target
// yields that target; zero, multiple, poisoned, or in-handover turns yield ""
// and consumers fail closed.
type turnDeliveryState struct {
	targets []string
	seen    map[string]struct{}
	// handover marks the unavailable window of an in-flight replace
	// transaction: the previous turn's targets are set aside until the exact
	// owner's acknowledgement commits the replacement, or the saved state is
	// restored on failure.
	handover *turnDeliveryHandover
	// poisoned marks a trusted delivery whose target could not be parsed: the
	// turn's primary destination is unresolvable, so the turn never yields a
	// default even if earlier targets looked normal.
	poisoned bool
}

type turnDeliveryHandover struct {
	ownerID string
	// replace marks a turn-switching transaction with an unavailable window;
	// append handovers only record the pending owner and keep targets live.
	replace bool
}

type savedTurnDelivery struct {
	ownerID  string
	targets  []string
	seen     map[string]struct{}
	poisoned bool
}

func (ap *Process) addTurnDeliveryTargetLocked(target string) {
	target = strings.TrimSpace(target)
	if target == "" {
		return
	}
	if ap.turnDelivery.seen == nil {
		ap.turnDelivery.seen = make(map[string]struct{})
	}
	if _, duplicate := ap.turnDelivery.seen[target]; duplicate {
		return
	}
	ap.turnDelivery.seen[target] = struct{}{}
	ap.turnDelivery.targets = append(ap.turnDelivery.targets, target)
}

func (ap *Process) addTurnDeliveryLocked(target string) {
	target = strings.TrimSpace(target)
	if target == "" {
		// A trusted delivery with a blank target introduces an unknown
		// destination the set cannot attribute, so the turn can no longer
		// prove "exactly one target": poison it like an unparseable one.
		ap.turnDelivery.poisoned = true
		return
	}
	if _, err := domain.ParseMessageTargetReference(target); err != nil {
		// A trusted delivery whose target cannot be parsed leaves the turn's
		// primary destination unresolvable: poison the turn so it can never
		// degrade into a confident single-target default.
		ap.turnDelivery.poisoned = true
		return
	}
	ap.addTurnDeliveryTargetLocked(target)
}

func (ap *Process) resetTurnDeliveryLocked() {
	ap.turnDelivery.targets = nil
	clear(ap.turnDelivery.seen)
	ap.turnDelivery.handover = nil
	ap.turnDelivery.poisoned = false
}

func (ap *Process) beginTurnDeliveryHandoverLocked(ownerID string) savedTurnDelivery {
	ownerID = strings.TrimSpace(ownerID)
	saved := savedTurnDelivery{
		ownerID:  ownerID,
		targets:  ap.turnDelivery.targets,
		seen:     ap.turnDelivery.seen,
		poisoned: ap.turnDelivery.poisoned,
	}
	ap.turnDelivery.targets = nil
	ap.turnDelivery.seen = nil
	ap.turnDelivery.handover = &turnDeliveryHandover{ownerID: ownerID, replace: true}
	return saved
}

// restoreTurnDeliveryLocked aborts one replace transaction and may only roll
// back a handover this exact save started. An already-committed transaction
// (its ack landed inside the RPC before the RPC reported failure) has cleared
// the handover and therefore stays committed.
func (ap *Process) restoreTurnDeliveryLocked(saved savedTurnDelivery) {
	h := ap.turnDelivery.handover
	if h == nil || !h.replace || h.ownerID != saved.ownerID {
		return
	}
	ap.turnDelivery.targets = saved.targets
	ap.turnDelivery.seen = saved.seen
	ap.turnDelivery.poisoned = saved.poisoned
	ap.turnDelivery.handover = nil
}

func (ap *Process) beginTurnDeliveryAppendLocked(ownerID string) {
	ap.turnDelivery.handover = &turnDeliveryHandover{ownerID: strings.TrimSpace(ownerID)}
}

func (ap *Process) commitTurnDeliveryAckLocked(ownerID string, target string) {
	if ap.stopping || ap.state == processStateStopped {
		return
	}
	ownerID = strings.TrimSpace(ownerID)
	h := ap.turnDelivery.handover
	if h != nil && h.ownerID != "" && ownerID != "" && h.ownerID != ownerID {
		// A late ack for a different pending input may append its target but
		// never finishes another delivery's transaction.
		ap.addTurnDeliveryLocked(target)
		return
	}
	ap.turnDelivery.handover = nil
	ap.addTurnDeliveryLocked(target)
}

// CurrentTurnDeliveryTarget returns the single trusted target of the current
// authoritative turn, or "" when the turn has zero distinct targets, several,
// a poisoned delivery, or an in-flight replace handover. It reports the raw
// target kind-agnostically: whether a target can seed a given default (for
// example task creation) and which error to surface is the consumer's
// decision, not this state machine's. Consumers must fail closed on empty.
// Generation fencing against replaced processes happens at the dispatcher.
func (ap *Process) CurrentTurnDeliveryTarget() string {
	if ap == nil {
		return ""
	}
	ap.mu.Lock()
	defer ap.mu.Unlock()
	return ap.currentTurnDeliveryTargetLocked()
}

func (ap *Process) currentTurnDeliveryTargetLocked() string {
	if (ap.turnDelivery.handover != nil && ap.turnDelivery.handover.replace) || ap.turnDelivery.poisoned || len(ap.turnDelivery.targets) != 1 {
		return ""
	}
	return ap.turnDelivery.targets[0]
}
