package dispatcher

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/desktop/internal/memorysearch"
)

// MemorySearchAvailable checks the latest authoritative refresh snapshot
// against Manager.activeFP. It performs no provider HTTP or filesystem I/O on
// the MCP hot path. A stale snapshot can only false-negative: every revoke,
// rebind, reset, and purge clears Manager.activeFP before returning.
func (d *Dispatcher) MemorySearchAvailable(ctx context.Context, routeAgentID string) bool {
	_, _, err := d.memorySearchSource(ctx, routeAgentID)
	return err == nil
}

func (d *Dispatcher) MemorySearch(
	ctx context.Context,
	routeAgentID string,
	request memorysearch.SearchRequest,
) (memorysearch.SearchResponse, error) {
	rec, _, err := d.memorySearchSource(ctx, routeAgentID)
	if err != nil {
		return memorysearch.SearchResponse{}, err
	}
	// Freshness is a non-blocking hint; Search uses the last serviceable
	// generation and never waits for the reconcile pass.
	rec.CheckFreshness(ctx)
	return memorysearch.Search(ctx, rec, request)
}

func (d *Dispatcher) RebuildMemorySearch(ctx context.Context, agentID string) error {
	agentID = strings.TrimSpace(agentID)
	lookupMu := d.agentMemoryLookupMutex(agentID)
	lookupMu.Lock()
	defer lookupMu.Unlock()
	reg, err := d.snapshotMemorySearch(ctx)
	if err != nil || reg == nil {
		return errors.New("memory search: manager unavailable")
	}
	return reg.rebuildAgent(agentID)
}

func (d *Dispatcher) ClearMemorySearch(ctx context.Context, agentID string) error {
	agentID = strings.TrimSpace(agentID)
	lookupMu := d.agentMemoryLookupMutex(agentID)
	lookupMu.Lock()
	defer lookupMu.Unlock()
	reg, err := d.snapshotMemorySearch(ctx)
	if err != nil || reg == nil || reg.manager == nil {
		return errors.New("memory search: manager unavailable")
	}
	// Clear is derivative-only: deny and drain the writer, purge
	// <workDir>/memory_search, and leave source <workDir>/memory intact.
	reg.denyAgent(agentID)
	if err := reg.manager.PurgeAgent(ctx, agentID); err != nil {
		return err
	}
	reg.dropAgentEntries(agentID)
	// Clear is not revoke. Re-resolve the binding while still holding the same
	// per-agent authority lock; only an authoritative Bound result reopens the
	// gate and starts a fresh derivative.
	d.memoryAccessMu.RLock()
	provider := d.memoryAccess
	d.memoryAccessMu.RUnlock()
	if provider == nil {
		return nil
	}
	access, state, err := provider.Lookup(ctx, agentID)
	if err != nil || state != MemoryAccessBound {
		return err
	}
	reg.setSourceTrusted(agentID, true)
	// Clear never touches source Memory, so a valid existing retention
	// marker is sufficient to reconstruct the derivative directly. Propagate
	// validation/Start failures instead of reporting a false successful clear.
	_, _, err = reg.getOrCreate(ctx, agentID, access)
	return err
}

func (d *Dispatcher) memorySearchSource(
	ctx context.Context,
	routeAgentID string,
) (*memorysearch.Reconciler, *memorysearch.Binding, error) {
	localAgentID := d.memorySearchResolvedAgentID(routeAgentID)
	if localAgentID == "" {
		return nil, nil, errors.New("memory search: authenticated agent session is unavailable")
	}
	reg, err := d.snapshotMemorySearch(ctx)
	if err != nil || reg == nil || reg.manager == nil {
		return nil, nil, errors.New("memory search: manager unavailable")
	}
	binding := reg.eligibleBinding(localAgentID)
	if binding == nil {
		return nil, nil, errors.New("memory search: binding is not authoritatively available")
	}
	active := reg.manager.ResolveBinding(localAgentID)
	if active == nil || active.Fingerprint() != binding.Fingerprint() {
		return nil, nil, fmt.Errorf("memory search: active binding mismatch")
	}
	rec := reg.manager.ActiveReconciler(localAgentID)
	if rec == nil {
		return nil, nil, errors.New("memory search: active reconciler unavailable")
	}
	return rec, binding, nil
}

func (d *Dispatcher) memorySearchResolvedAgentID(routeAgentID string) string {
	routeAgentID = strings.TrimSpace(routeAgentID)
	if routeAgentID == "" {
		return ""
	}
	if reg, _ := d.snapshotMemorySearch(context.Background()); reg != nil {
		if localAgentID := reg.resolvedAgentID(routeAgentID); localAgentID != routeAgentID {
			return localAgentID
		}
	}
	if sess, ok := d.sessionManager.FindByLookupID(routeAgentID); ok && sess != nil {
		sess.mu.Lock()
		id := strings.TrimSpace(sess.agent.ID)
		sess.mu.Unlock()
		if id != "" {
			return id
		}
	}
	return routeAgentID
}
