package routing

import (
	"fmt"
	"log"
)

// ── Fallback Runner ─────────────────────────

// DispatchFunc is called by the fallback runner to attempt a request
// against a specific candidate. Returns nil on success, error on failure.
// The caller is responsible for setting up SSE headers, streaming, etc.
// This is NOT the actual dispatch — it's the "try this candidate" callback.
type DispatchFunc func(candidate Candidate) error

// RunWithFallback tries candidates in order until one succeeds.
// Returns the candidate that succeeded, the decision (updated with fallback
// depth), and any error if all candidates fail.
//
// maxRetries controls how many fallback attempts are made beyond the first.
// maxRetries=0 means only try the first candidate (no fallback).
// maxRetries=-1 means try all candidates.
func RunWithFallback(candidates []Candidate, decision *Decision, maxRetries int, dispatch DispatchFunc) (Candidate, *Decision, error) {
	if len(candidates) == 0 {
		return Candidate{}, decision, fmt.Errorf("no routing candidates available")
	}

	// Determine how many candidates to try
	limit := 1 // just the first
	if maxRetries < 0 {
		limit = len(candidates) // try all
	} else if maxRetries > 0 {
		limit = 1 + maxRetries
		if limit > len(candidates) {
			limit = len(candidates)
		}
	}

	var lastErr error
	for i := 0; i < limit; i++ {
		c := candidates[i]
		err := dispatch(c)
		if err == nil {
			// Success
			if decision != nil {
				decision.Selected = c.ConfigID
				decision.FallbackDepth = i
			}
			return c, decision, nil
		}

		lastErr = err
		if i < limit-1 {
			log.Printf("[routing] candidate %s (%s) failed: %v — trying next", c.ConfigID, c.ProviderID, err)
		}
	}

	return Candidate{}, decision, fmt.Errorf("all %d routing candidates failed; last error: %w", limit, lastErr)
}
