/**
 * Marketplace node router.
 *
 * Selects the best available operator node for a marketplace request.
 * Pipeline: filter by model → filter by online → filter price → filter reputation → rank by score → select top.
 */

type ReputationTier = "Unverified" | "Standard" | "Verified" | "Elite";

/**
 * Minimum score required to satisfy a reputation tier filter.
 */
const TIER_MIN_SCORES: Record<ReputationTier, number> = {
  Unverified: 0,
  Standard: 50,
  Verified: 80,
  Elite: 95,
};

interface MarketplaceNode {
  deviceId: string;
  userId: string;
  name: string;
  status: "online" | "offline" | "degraded";
  tunnelUrl: string;
  models: string[];
  pricePerMToken: number;
  reputationScore: number; // 0-100
  latencyP95Ms: number;
  uptimePercent: number;
}

interface RouteResult {
  node: MarketplaceNode;
  estimatedCostPerMToken: number;
  reason: string;
}

interface RouteRequest {
  model: string;
  maxPricePerMToken?: number;
  preferLowLatency?: boolean;
  minReputation?: number;
  /** Filter by minimum reputation tier. Takes priority over minReputation if both set. */
  minReputationTier?: ReputationTier;
}

/**
 * Filter nodes that have the requested model available.
 */
function filterByModel(nodes: MarketplaceNode[], model: string): MarketplaceNode[] {
  return nodes.filter((n) =>
    n.models.some(
      (m) => m.toLowerCase() === model.toLowerCase() || m.toLowerCase().startsWith(model.toLowerCase()),
    ),
  );
}

/**
 * Filter to only online nodes.
 */
function filterByStatus(nodes: MarketplaceNode[]): MarketplaceNode[] {
  return nodes.filter((n) => n.status === "online");
}

/**
 * Filter by maximum price.
 */
function filterByPrice(nodes: MarketplaceNode[], maxPrice?: number): MarketplaceNode[] {
  if (maxPrice === undefined) return nodes;
  return nodes.filter((n) => n.pricePerMToken <= maxPrice);
}

/**
 * Filter by minimum reputation score.
 */
function filterByReputation(nodes: MarketplaceNode[], minReputation?: number): MarketplaceNode[] {
  if (minReputation === undefined) return nodes;
  return nodes.filter((n) => n.reputationScore >= minReputation);
}

/**
 * Filter by minimum reputation tier.
 *
 * Converts the tier name (e.g. "Standard") to a minimum score threshold
 * and filters nodes below that threshold.
 */
function filterByReputationTier(nodes: MarketplaceNode[], minTier?: ReputationTier): MarketplaceNode[] {
  if (!minTier) return nodes;
  const minScore = TIER_MIN_SCORES[minTier] ?? 0;
  return nodes.filter((n) => n.reputationScore >= minScore);
}

/**
 * Score a node for ranking. Higher is better.
 *
 * Scoring formula:
 * - Reputation: 40% weight (0-100 → 0-40)
 * - Price: 30% weight (cheaper is better, inverse scale)
 * - Latency: 20% weight (lower is better, inverse scale) — 30% if preferLowLatency
 * - Uptime: 10% weight (higher is better)
 */
function scoreNode(node: MarketplaceNode, preferLowLatency: boolean = false): number {
  const reputationScore = (node.reputationScore / 100) * 40;

  // Price score: $0.01 = 30 points, $1.00 = 0 points (inverse linear)
  const priceScore = Math.max(0, 30 * (1 - (node.pricePerMToken - 0.01) / 0.99));

  // Latency score: 0ms = weight points, 5000ms = 0 points
  const latencyWeight = preferLowLatency ? 30 : 20;
  const latencyScore = Math.max(0, latencyWeight * (1 - node.latencyP95Ms / 5000));

  // Uptime score
  const uptimeScore = (node.uptimePercent / 100) * 10;

  return reputationScore + priceScore + latencyScore + uptimeScore;
}

/**
 * Route a marketplace request to the best available node.
 *
 * Pipeline: filter model → filter online → filter price → filter reputation → rank → select.
 */
export function routeMarketplaceRequest(
  nodes: MarketplaceNode[],
  request: RouteRequest,
): RouteResult | null {
  // Step 1: Filter
  let candidates = filterByModel(nodes, request.model);
  if (candidates.length === 0) return null;

  candidates = filterByStatus(candidates);
  if (candidates.length === 0) return null;

  candidates = filterByPrice(candidates, request.maxPricePerMToken);
  if (candidates.length === 0) return null;

  candidates = filterByReputation(candidates, request.minReputation);
  if (candidates.length === 0) return null;

  candidates = filterByReputationTier(candidates, request.minReputationTier);
  if (candidates.length === 0) return null;

  // Step 2: Rank
  const scored = candidates
    .map((node) => ({ node, score: scoreNode(node, request.preferLowLatency) }))
    .sort((a, b) => b.score - a.score);

  // Step 3: Select top
  const best = scored[0];

  return {
    node: best.node,
    estimatedCostPerMToken: best.node.pricePerMToken,
    reason: `Selected ${best.node.name} (score: ${best.score.toFixed(1)}, reputation: ${best.node.reputationScore}, price: $${best.node.pricePerMToken}/1M tokens)`,
  };
}

export type { MarketplaceNode, RouteResult, RouteRequest, ReputationTier };
export {
  filterByModel,
  filterByStatus,
  filterByPrice,
  filterByReputation,
  filterByReputationTier,
  scoreNode,
  TIER_MIN_SCORES,
};
