#!/usr/bin/env python3
"""
GENESIS TASK ROUTER
====================
Classifies incoming tasks and routes them to the optimal executor.

Routing table:
  Simple file/text ops       → Claude Haiku       (cost: ~$0.001)
  Standard implementation    → Claude Sonnet       (cost: ~$0.01)
  Complex reasoning/arch     → Claude Opus         (cost: ~$0.10)
  Bulk parallel execution    → Gemini Swarm        (cost: ~$0.002/task)
  100-agent parallelism      → Kimi K2.5           (cost: minimal)
  Browser automation         → Playwright MCP      (cost: 0)
  Memory read/write          → RLM Bloodstream     (cost: 0)
  Research/intelligence      → Research Scout      (Haiku)

Decision logic:
  1. Check for browser keywords → Playwright MCP
  2. Check for memory keywords → RLM
  3. Estimate complexity score → select model tier
  4. If bulk/parallel → Gemini swarm or Kimi
  5. Otherwise → Claude tier based on complexity

Usage:
    from core.task_router import TaskRouter

    router = TaskRouter()
    executor = router.classify_and_route("implement feature X")
    explanation = router.explain_routing("implement feature X")
"""

import re
from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Optional, Tuple
from pathlib import Path


class ExecutorType(Enum):
    CLAUDE_OPUS = "claude-opus"
    CLAUDE_SONNET = "claude-sonnet"
    CLAUDE_HAIKU = "claude-haiku"
    GEMINI_SWARM = "gemini-swarm"
    KIMI_K2 = "kimi-k2"
    PLAYWRIGHT = "playwright"
    RLM_BLOODSTREAM = "rlm"


@dataclass
class RoutingDecision:
    executor: ExecutorType
    confidence: float          # 0.0 - 1.0
    complexity_score: int      # 1 (trivial) to 10 (max)
    reason: str
    estimated_cost_usd: float
    parallel_workers: int = 1  # >1 means swarm mode


# Keywords that trigger specific routes regardless of complexity
BROWSER_KEYWORDS = [
    "login", "click", "browser", "playwright", "navigate", "form fill",
    "website", "webpage", "portal", "dashboard", "ghl", "telnyx portal",
    "cloudflare", "stripe portal", "scroll", "screenshot", "dom",
    "authenticate", "sign in", "open browser", "web automation"
]

MEMORY_KEYWORDS = [
    "memory", "rlm", "bloodstream", "remember", "recall", "knowledge graph",
    "kg entity", "axiom", "titan memory", "session context", "sunaiva"
]

RESEARCH_KEYWORDS = [
    "research", "find", "search", "discover", "scout", "explore",
    "gather intel", "investigate", "what is", "who is", "analyze market"
]

BULK_KEYWORDS = [
    "swarm", "parallel", "batch", "100 agents", "kimi", "fan out",
    "multiple", "all domains", "bulk", "mass", "fleet", "scale"
]

# Complexity signal words → points added to complexity score
COMPLEXITY_SIGNALS = {
    # High complexity (+3)
    "architect": 3, "orchestrat": 3, "design": 3, "refactor": 3,
    "security": 3, "cryptograph": 3, "optimize": 3, "strategy": 3,
    "multi-agent": 3, "distributed": 3,

    # Medium complexity (+2)
    "implement": 2, "build": 2, "create": 2, "develop": 2,
    "integrate": 2, "pipeline": 2, "system": 2, "api": 2,
    "database": 2, "async": 2, "concurrent": 2,

    # Low complexity (+1)
    "update": 1, "fix": 1, "add": 1, "write": 1, "list": 1,
    "show": 1, "read": 1, "format": 1, "rename": 1,

    # Trivial (-1)
    "what": -1, "when": -1, "where": -1, "simple": -1
}

# Cost estimates per task (USD)
COST_ESTIMATES = {
    ExecutorType.CLAUDE_OPUS: 0.15,
    ExecutorType.CLAUDE_SONNET: 0.02,
    ExecutorType.CLAUDE_HAIKU: 0.001,
    ExecutorType.GEMINI_SWARM: 0.003,
    ExecutorType.KIMI_K2: 0.001,
    ExecutorType.PLAYWRIGHT: 0.0,
    ExecutorType.RLM_BLOODSTREAM: 0.0,
}


class TaskRouter:
    """
    Classifies and routes Genesis tasks to the optimal executor.

    Implements RULE 9 (Native-First) and RULE 10 (Capability-First):
    - Always checks if a simpler/cheaper executor can do the job
    - Prefers browser automation over asking Kinan
    - Prefers Gemini swarm for bulk work
    - Uses Claude Opus only for genuine reasoning tasks
    """

    def classify_and_route(
        self,
        task: str,
        complexity_hint: str = "auto",
        force_parallel: bool = False
    ) -> ExecutorType:
        """
        Main routing method. Returns the optimal ExecutorType.

        Args:
            task: Natural language task description
            complexity_hint: "low", "medium", "high", "auto"
            force_parallel: If True, prefer swarm/Kimi for parallelism
        """
        decision = self._make_routing_decision(task, complexity_hint, force_parallel)
        return decision.executor

    def explain_routing(self, task: str, complexity_hint: str = "auto") -> str:
        """Returns human-readable explanation of routing decision."""
        decision = self._make_routing_decision(task, complexity_hint)
        return (
            f"Executor: {decision.executor.value}\n"
            f"Complexity: {decision.complexity_score}/10\n"
            f"Confidence: {decision.confidence:.0%}\n"
            f"Est. Cost: ${decision.estimated_cost_usd:.4f}\n"
            f"Workers: {decision.parallel_workers}\n"
            f"Reason: {decision.reason}"
        )

    def _make_routing_decision(
        self,
        task: str,
        complexity_hint: str = "auto",
        force_parallel: bool = False
    ) -> RoutingDecision:
        """Core routing logic."""
        task_lower = task.lower()

        # PRIORITY 1: Browser automation (RULE 11 — never ask Kinan)
        if self._matches_any(task_lower, BROWSER_KEYWORDS):
            return RoutingDecision(
                executor=ExecutorType.PLAYWRIGHT,
                confidence=0.95,
                complexity_score=3,
                reason="Browser keywords detected — Playwright MCP handles this autonomously",
                estimated_cost_usd=COST_ESTIMATES[ExecutorType.PLAYWRIGHT]
            )

        # PRIORITY 2: Memory operations → RLM bloodstream
        if self._matches_any(task_lower, MEMORY_KEYWORDS):
            return RoutingDecision(
                executor=ExecutorType.RLM_BLOODSTREAM,
                confidence=0.90,
                complexity_score=2,
                reason="Memory/KG operation — RLM bloodstream handles this",
                estimated_cost_usd=COST_ESTIMATES[ExecutorType.RLM_BLOODSTREAM]
            )

        # PRIORITY 3: Bulk/swarm work
        if self._matches_any(task_lower, BULK_KEYWORDS) or force_parallel:
            # Check if Kimi makes more sense (100+ agent scale)
            if "100" in task_lower or "fleet" in task_lower or "kimi" in task_lower:
                return RoutingDecision(
                    executor=ExecutorType.KIMI_K2,
                    confidence=0.85,
                    complexity_score=4,
                    reason="Massive parallel scale detected — Kimi K2.5 swarm (100 agents)",
                    estimated_cost_usd=COST_ESTIMATES[ExecutorType.KIMI_K2],
                    parallel_workers=100
                )
            return RoutingDecision(
                executor=ExecutorType.GEMINI_SWARM,
                confidence=0.90,
                complexity_score=5,
                reason="Bulk/parallel work — Gemini swarm executes in parallel",
                estimated_cost_usd=COST_ESTIMATES[ExecutorType.GEMINI_SWARM],
                parallel_workers=10
            )

        # PRIORITY 4: Research tasks → Research Scout (Haiku)
        if self._matches_any(task_lower, RESEARCH_KEYWORDS) and complexity_hint == "low":
            return RoutingDecision(
                executor=ExecutorType.CLAUDE_HAIKU,
                confidence=0.80,
                complexity_score=2,
                reason="Research/discovery task — Haiku research-scout handles this cost-effectively",
                estimated_cost_usd=COST_ESTIMATES[ExecutorType.CLAUDE_HAIKU]
            )

        # PRIORITY 5: Complexity-based Claude tier selection
        complexity = self._estimate_complexity(task, complexity_hint)

        if complexity >= 7:
            return RoutingDecision(
                executor=ExecutorType.CLAUDE_OPUS,
                confidence=0.85,
                complexity_score=complexity,
                reason=f"High complexity ({complexity}/10) — Opus required for reasoning depth",
                estimated_cost_usd=COST_ESTIMATES[ExecutorType.CLAUDE_OPUS]
            )
        elif complexity >= 4:
            return RoutingDecision(
                executor=ExecutorType.CLAUDE_SONNET,
                confidence=0.88,
                complexity_score=complexity,
                reason=f"Medium complexity ({complexity}/10) — Sonnet handles implementation well",
                estimated_cost_usd=COST_ESTIMATES[ExecutorType.CLAUDE_SONNET]
            )
        else:
            return RoutingDecision(
                executor=ExecutorType.CLAUDE_HAIKU,
                confidence=0.92,
                complexity_score=complexity,
                reason=f"Low complexity ({complexity}/10) — Haiku is cost-optimal here",
                estimated_cost_usd=COST_ESTIMATES[ExecutorType.CLAUDE_HAIKU]
            )

    def _estimate_complexity(self, task: str, hint: str = "auto") -> int:
        """
        Score task complexity on 1-10 scale.
        Uses keyword signals + length + hint override.
        """
        if hint == "high":
            return 8
        if hint == "low":
            return 2
        if hint == "medium":
            return 5

        task_lower = task.lower()
        score = 3  # baseline

        # Apply keyword signals
        for keyword, points in COMPLEXITY_SIGNALS.items():
            if keyword in task_lower:
                score += points

        # Length signal (longer task description = more complex)
        word_count = len(task.split())
        if word_count > 100:
            score += 2
        elif word_count > 50:
            score += 1

        # Multi-step indicators
        step_indicators = ["first", "then", "finally", "also", "and then", "step"]
        step_count = sum(1 for s in step_indicators if s in task_lower)
        score += min(step_count, 3)

        return max(1, min(10, score))

    def _matches_any(self, text: str, keywords: List[str]) -> bool:
        """Check if text contains any keyword from list."""
        return any(kw in text for kw in keywords)

    def get_routing_table(self) -> Dict[str, str]:
        """Returns the full routing table for documentation/debugging."""
        return {
            "browser/portal/login": "playwright",
            "memory/kg/rlm/bloodstream": "rlm",
            "bulk/swarm/parallel/batch": "gemini-swarm",
            "100-agent/fleet/kimi": "kimi-k2",
            "complexity 7-10": "claude-opus",
            "complexity 4-6": "claude-sonnet",
            "complexity 1-3": "claude-haiku"
        }

    def batch_route(self, tasks: List[str]) -> List[Tuple[str, ExecutorType]]:
        """Route a batch of tasks, returning (task, executor) pairs."""
        return [(task, self.classify_and_route(task)) for task in tasks]


if __name__ == "__main__":
    import sys

    router = TaskRouter()

    test_tasks = [
        "Login to GHL and activate the review workflow",
        "Remember this axiom: always check existing first",
        "Implement the voice agent registration endpoint in FastAPI",
        "Design the multi-agent orchestration architecture for ReceptionistAI",
        "Batch scrape 500 tradie businesses from Yellow Pages",
        "What is the capital of Australia?",
        "Research Telnyx pricing for Australian phone numbers",
    ]

    if len(sys.argv) > 1:
        task = " ".join(sys.argv[1:])
        print(router.explain_routing(task))
    else:
        print("Genesis Task Router — Routing Table Demo\n")
        for task in test_tasks:
            executor = router.classify_and_route(task)
            score = router._estimate_complexity(task)
            print(f"  [{executor.value:20s}] (complexity={score}) {task[:60]}")
