import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { logger } from "../monitoring/logger.js";
import { EventEmitter } from "events";
class QueryRouter extends EventEmitter {
  tiers = /* @__PURE__ */ new Map();
  metrics;
  decisionCache = /* @__PURE__ */ new Map();
  cacheExpiration = 6e4;
  // 1 minute
  maxCacheSize = 1e3;
  constructor() {
    super();
    this.metrics = {
      totalQueries: 0,
      queriesByTier: /* @__PURE__ */ new Map(),
      queriesByType: /* @__PURE__ */ new Map(),
      averageLatency: 0,
      latencyByTier: /* @__PURE__ */ new Map(),
      errorsByTier: /* @__PURE__ */ new Map(),
      cacheHitRate: 0,
      routingDecisions: 0
    };
  }
  /**
   * Register a storage tier with the router
   */
  registerTier(tier) {
    this.tiers.set(tier.name, tier);
    logger.info(
      `Registered storage tier: ${tier.name} (priority: ${tier.priority})`
    );
    this.emit("tierRegistered", tier);
  }
  /**
   * Remove a storage tier from the router
   */
  unregisterTier(tierName) {
    const tier = this.tiers.get(tierName);
    if (tier) {
      this.tiers.delete(tierName);
      logger.info(`Unregistered storage tier: ${tierName}`);
      this.emit("tierUnregistered", tier);
    }
  }
  /**
   * Route a query to the most appropriate storage tier
   */
  async route(operation, context, executor) {
    const startTime = Date.now();
    this.metrics.totalQueries++;
    this.metrics.queriesByType.set(
      context.queryType,
      (this.metrics.queriesByType.get(context.queryType) || 0) + 1
    );
    try {
      const decision = await this.makeRoutingDecision(operation, context);
      try {
        const result = await this.executeOnTier(decision.primaryTier, executor);
        this.updateMetrics(decision.primaryTier.name, startTime, true);
        return result;
      } catch (error) {
        logger.warn(
          `Query failed on primary tier ${decision.primaryTier.name}:`,
          error
        );
        this.updateMetrics(decision.primaryTier.name, startTime, false);
        for (const fallbackTier of decision.fallbackTiers) {
          try {
            logger.info(`Attempting fallback to tier: ${fallbackTier.name}`);
            const result = await this.executeOnTier(fallbackTier, executor);
            this.updateMetrics(fallbackTier.name, startTime, true);
            return result;
          } catch (fallbackError) {
            logger.warn(
              `Query failed on fallback tier ${fallbackTier.name}:`,
              fallbackError
            );
            this.updateMetrics(fallbackTier.name, startTime, false);
          }
        }
        throw error;
      }
    } catch (error) {
      logger.error("Query routing failed:", error);
      this.emit("routingError", { operation, context, error });
      throw error;
    }
  }
  /**
   * Make routing decision based on query context
   */
  async makeRoutingDecision(operation, context) {
    const cacheKey = this.generateCacheKey(operation, context);
    const cached = this.decisionCache.get(cacheKey);
    if (cached && Date.now() - cached.estimatedLatency < this.cacheExpiration) {
      this.metrics.cacheHitRate = (this.metrics.cacheHitRate * this.metrics.routingDecisions + 1) / (this.metrics.routingDecisions + 1);
      return cached;
    }
    this.metrics.routingDecisions++;
    const evaluations = [];
    for (const [name, tier] of this.tiers) {
      const score = await this.evaluateTier(tier, operation, context);
      const rationale = this.generateRationale(tier, operation, context, score);
      evaluations.push({ tier, score, rationale });
    }
    evaluations.sort((a, b) => b.score - a.score);
    if (evaluations.length === 0) {
      throw new Error("No storage tiers available for routing");
    }
    const primaryEval = evaluations[0];
    const fallbackTiers = evaluations.slice(1).map((evaluation) => evaluation.tier);
    const decision = {
      primaryTier: primaryEval.tier,
      fallbackTiers,
      rationale: primaryEval.rationale,
      confidence: primaryEval.score,
      estimatedLatency: this.estimateLatency(
        primaryEval.tier,
        operation,
        context
      ),
      cacheRecommendation: this.recommendCacheStrategy(
        primaryEval.tier,
        context
      )
    };
    this.cacheDecision(cacheKey, decision);
    logger.debug(
      `Routing decision: ${decision.primaryTier.name} (confidence: ${decision.confidence.toFixed(2)})`
    );
    this.emit("routingDecision", { operation, context, decision });
    return decision;
  }
  /**
   * Evaluate how well a tier fits the query requirements
   */
  async evaluateTier(tier, operation, context) {
    let score = 0;
    let maxScore = 0;
    for (const rule of tier.config.routingRules) {
      maxScore += rule.weight;
      if (this.evaluateRule(rule, operation, context, tier)) {
        score += rule.weight;
      }
    }
    if (tier.config.preferredOperations.includes(context.queryType)) {
      score += 0.2;
      maxScore += 0.2;
    }
    if (context.requiredFeatures) {
      const supportedFeatures = context.requiredFeatures.filter(
        (feature) => tier.config.supportedFeatures.includes(feature)
      );
      if (supportedFeatures.length === context.requiredFeatures.length) {
        score += 0.3;
      }
      maxScore += 0.3;
    }
    const currentLoad = await this.getCurrentLoad(tier);
    if (tier.config.maxThroughput && currentLoad < tier.config.maxThroughput * 0.8) {
      score += 0.1;
    }
    maxScore += 0.1;
    if (await this.isWithinCapacity(tier)) {
      score += 0.1;
    }
    maxScore += 0.1;
    return maxScore > 0 ? score / maxScore : 0;
  }
  /**
   * Evaluate a single routing rule
   */
  evaluateRule(rule, operation, context, tier) {
    let actualValue;
    switch (rule.condition) {
      case "age":
        if (context.frames && context.frames.length > 0) {
          const avgAge = context.frames.reduce(
            (sum, frame) => sum + (Date.now() - frame.created_at),
            0
          ) / context.frames.length;
          actualValue = avgAge;
        } else if (context.timeRange) {
          actualValue = Date.now() - context.timeRange.end.getTime();
        } else {
          return false;
        }
        break;
      case "query_type":
        actualValue = context.queryType;
        break;
      case "feature":
        actualValue = context.requiredFeatures || [];
        break;
      case "priority":
        actualValue = context.priority || "medium";
        break;
      case "size":
        actualValue = context.frames ? context.frames.length : 0;
        break;
      default:
        return false;
    }
    return this.compareValues(actualValue, rule.operator, rule.value);
  }
  /**
   * Compare values based on operator
   */
  compareValues(actual, operator, expected) {
    switch (operator) {
      case ">":
        return actual > expected;
      case "<":
        return actual < expected;
      case "=":
      case "==":
        return actual === expected;
      case "!=":
        return actual !== expected;
      case "in":
        return Array.isArray(expected) && expected.includes(actual);
      case "not_in":
        return Array.isArray(expected) && !expected.includes(actual);
      case "contains":
        return Array.isArray(actual) && actual.some((item) => expected.includes(item));
      default:
        return false;
    }
  }
  /**
   * Execute query on specific tier
   */
  async executeOnTier(tier, executor) {
    const startTime = Date.now();
    try {
      const result = await executor(tier.adapter);
      const duration = Date.now() - startTime;
      logger.debug(`Query executed on tier ${tier.name} in ${duration}ms`);
      this.emit("queryExecuted", {
        tierName: tier.name,
        duration,
        success: true
      });
      return result;
    } catch (error) {
      const duration = Date.now() - startTime;
      logger.error(
        `Query failed on tier ${tier.name} after ${duration}ms:`,
        error
      );
      this.emit("queryExecuted", {
        tierName: tier.name,
        duration,
        success: false,
        error
      });
      throw error;
    }
  }
  /**
   * Generate cache key for routing decisions
   */
  generateCacheKey(operation, context) {
    const keyParts = [
      operation,
      context.queryType,
      context.priority || "medium",
      (context.requiredFeatures || []).sort().join(","),
      context.timeRange ? `${context.timeRange.start.getTime()}-${context.timeRange.end.getTime()}` : ""
    ];
    return keyParts.join("|");
  }
  /**
   * Cache routing decision
   */
  cacheDecision(key, decision) {
    if (this.decisionCache.size >= this.maxCacheSize) {
      const firstKey = this.decisionCache.keys().next().value;
      this.decisionCache.delete(firstKey);
    }
    this.decisionCache.set(key, decision);
  }
  /**
   * Estimate query latency for a tier
   */
  estimateLatency(tier, operation, context) {
    const baseLatency = this.metrics.latencyByTier.get(tier.name) || tier.config.maxLatency || 100;
    let multiplier = 1;
    switch (context.queryType) {
      case "search":
        multiplier = 1.5;
        break;
      case "analytics":
        multiplier = 2;
        break;
      case "bulk":
        multiplier = 3;
        break;
      default:
        multiplier = 1;
    }
    return baseLatency * multiplier;
  }
  /**
   * Recommend cache strategy for the context
   */
  recommendCacheStrategy(tier, context) {
    if (context.cacheStrategy && context.cacheStrategy !== "none") {
      return context.cacheStrategy;
    }
    if (tier.name === "hot" || tier.name === "memory") {
      return "read_write";
    } else if (context.queryType === "read") {
      return "read";
    }
    return "none";
  }
  /**
   * Generate human-readable rationale for routing decision
   */
  generateRationale(tier, operation, context, score) {
    const reasons = [];
    if (tier.config.preferredOperations.includes(context.queryType)) {
      reasons.push(`optimized for ${context.queryType} operations`);
    }
    if (context.requiredFeatures?.every(
      (feature) => tier.config.supportedFeatures.includes(feature)
    )) {
      reasons.push(
        `supports all required features (${context.requiredFeatures.join(", ")})`
      );
    }
    if (score > 0.8) {
      reasons.push("high confidence match");
    } else if (score > 0.6) {
      reasons.push("good match");
    } else if (score > 0.4) {
      reasons.push("acceptable match");
    }
    return reasons.length > 0 ? reasons.join(", ") : "default tier selection";
  }
  /**
   * Get current load for a tier
   */
  async getCurrentLoad(tier) {
    return this.metrics.queriesByTier.get(tier.name) || 0;
  }
  /**
   * Check if tier is within capacity limits
   */
  async isWithinCapacity(tier) {
    try {
      const stats = await tier.adapter.getStats();
      if (tier.config.maxFrames && stats.totalFrames >= tier.config.maxFrames) {
        return false;
      }
      if (tier.config.maxSizeMB && stats.diskUsage >= tier.config.maxSizeMB * 1024 * 1024) {
        return false;
      }
      return true;
    } catch (error) {
      logger.warn(`Failed to check capacity for tier ${tier.name}:`, error);
      return true;
    }
  }
  /**
   * Update routing metrics
   */
  updateMetrics(tierName, startTime, success) {
    const duration = Date.now() - startTime;
    this.metrics.queriesByTier.set(
      tierName,
      (this.metrics.queriesByTier.get(tierName) || 0) + 1
    );
    if (success) {
      const currentAvg = this.metrics.latencyByTier.get(tierName) || 0;
      const count = this.metrics.queriesByTier.get(tierName) || 1;
      const newAvg = (currentAvg * (count - 1) + duration) / count;
      this.metrics.latencyByTier.set(tierName, newAvg);
      this.metrics.averageLatency = (this.metrics.averageLatency * (this.metrics.totalQueries - 1) + duration) / this.metrics.totalQueries;
    } else {
      this.metrics.errorsByTier.set(
        tierName,
        (this.metrics.errorsByTier.get(tierName) || 0) + 1
      );
    }
  }
  /**
   * Get current routing metrics
   */
  getMetrics() {
    const cacheRequests = this.metrics.routingDecisions;
    const cacheHits = cacheRequests - this.decisionCache.size;
    this.metrics.cacheHitRate = cacheRequests > 0 ? cacheHits / cacheRequests : 0;
    return { ...this.metrics };
  }
  /**
   * Get registered tiers
   */
  getTiers() {
    return Array.from(this.tiers.values()).sort(
      (a, b) => b.priority - a.priority
    );
  }
  /**
   * Clear routing decision cache
   */
  clearCache() {
    this.decisionCache.clear();
    logger.info("Routing decision cache cleared");
  }
  /**
   * Get tier by name
   */
  getTier(name) {
    return this.tiers.get(name);
  }
}
export {
  QueryRouter
};
//# sourceMappingURL=query-router.js.map
