import os
import json
import time
import logging
import threading
import subprocess
import platform as _platform
from pathlib import Path
from flask import Flask, request, jsonify, send_from_directory

from marziel.config import LLM_PORT, IDLE_TIMEOUT, VLLM_URL, VLLM_MODEL, AGENT_TIERS
from marziel.billing import billing_bp, read_license
from marziel.search import needs_web_search, search_duckduckgo, format_search_context
try:
    from marziel import __version__ as _MARZIEL_VERSION
except Exception:
    _MARZIEL_VERSION = "0.9.0"

logger = logging.getLogger("marziel.server")

# ── Static files directory (bundled React build) ──
STATIC_DIR = Path(__file__).parent / "static"

# ── Idle engine management ──
_last_request_time = time.time()
_watchdog_started = False

# ── Rate limiting (per-IP, in-memory) ──
from collections import defaultdict
_rate_buckets: dict[str, list[float]] = defaultdict(list)
_rate_lock = threading.Lock()
RATE_LIMIT = 20       # max requests per window
RATE_WINDOW = 60      # window in seconds

_RATE_LIMITED_PATHS = {"/llm/chat", "/api/checkout", "/api/webhook"}


def _is_rate_limited(ip: str, path: str) -> bool:
    """Check if IP exceeded rate limit for sensitive endpoints."""
    if path not in _RATE_LIMITED_PATHS:
        return False
    now = time.time()
    with _rate_lock:
        bucket = _rate_buckets[ip]
        # Purge old entries outside window
        _rate_buckets[ip] = [t for t in bucket if now - t < RATE_WINDOW]
        if len(_rate_buckets[ip]) >= RATE_LIMIT:
            return True
        _rate_buckets[ip].append(now)
    return False


def _get_resource_info():
    """Get current system resource usage."""
    info = {}
    try:
        import resource
        # Memory usage of this process (MB)
        info["server_rss_mb"] = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024 / 1024, 1)
    except Exception:
        pass

    # System RAM
    try:
        if _platform.system() == "Darwin":
            result = subprocess.run(
                ["sysctl", "-n", "hw.memsize"],
                capture_output=True, text=True, timeout=3
            )
            total_bytes = int(result.stdout.strip())
            info["system_ram_gb"] = round(total_bytes / 1024**3, 1)

            # Get available memory via vm_stat
            result = subprocess.run(["vm_stat"], capture_output=True, text=True, timeout=3)
            for line in result.stdout.split("\n"):
                if "Pages free" in line:
                    pages = int(line.split(":")[1].strip().rstrip("."))
                    info["available_ram_gb"] = round(pages * 4096 / 1024**3, 1)
                    break
        else:
            with open("/proc/meminfo") as f:
                for line in f:
                    if line.startswith("MemTotal"):
                        info["system_ram_gb"] = round(int(line.split()[1]) / 1024**2, 1)
                    elif line.startswith("MemAvailable"):
                        info["available_ram_gb"] = round(int(line.split()[1]) / 1024**2, 1)
    except Exception:
        pass

    # Engine process RAM
    try:
        result = subprocess.run(
            f"lsof -ti:{LLM_PORT} | head -1",
            shell=True, capture_output=True, text=True, timeout=3
        )
        pid = result.stdout.strip()
        if pid:
            result = subprocess.run(
                ["ps", "-o", "rss=", "-p", pid],
                capture_output=True, text=True, timeout=3
            )
            rss_kb = int(result.stdout.strip())
            info["engine_ram_mb"] = round(rss_kb / 1024, 0)
            info["engine_pid"] = int(pid)
    except Exception:
        pass

    info["idle_timeout_sec"] = IDLE_TIMEOUT
    info["idle_sec"] = round(time.time() - _last_request_time)
    return info


def _engine_running():
    """Check if MLX engine is running."""
    try:
        import urllib.request
        req = urllib.request.Request(f"http://localhost:{LLM_PORT}/v1/models")
        resp = urllib.request.urlopen(req, timeout=2)
        return resp.status == 200
    except Exception:
        return False


def _stop_engine():
    """Stop the MLX engine to free RAM."""
    try:
        subprocess.run(
            f"lsof -ti:{LLM_PORT} | xargs kill 2>/dev/null || true",
            shell=True, capture_output=True, timeout=5
        )
        logger.info(f"🛑 Engine stopped (idle > {IDLE_TIMEOUT}s) — RAM freed")
    except Exception:
        pass


def _start_idle_watchdog():
    """Background thread that stops engine after idle timeout."""
    global _watchdog_started
    if _watchdog_started or IDLE_TIMEOUT <= 0:
        return
    _watchdog_started = True

    def watchdog():
        while True:
            time.sleep(60)  # check every minute
            idle = time.time() - _last_request_time
            if idle > IDLE_TIMEOUT and _engine_running():
                logger.info(f"⏸️  No requests for {int(idle)}s — shutting down engine to save resources")
                _stop_engine()

    t = threading.Thread(target=watchdog, daemon=True, name="marziel-idle-watchdog")
    t.start()
    logger.info(f"♻️  Idle watchdog active (timeout: {IDLE_TIMEOUT}s)")


def _ensure_engine():
    """Ensure the MarzielEngine is loaded and ready."""
    try:
        from marziel.engine import get_engine
        engine = get_engine()
        if engine.is_ready:
            return True
        logger.info("🔄 Loading engine for incoming request...")
        return engine.load()
    except Exception as e:
        logger.warning(f"Engine load failed: {e}")
    return False


def create_app():
    """Create the Flask application."""
    app = Flask(__name__, static_folder=str(STATIC_DIR), static_url_path="")

    # Start idle watchdog
    _start_idle_watchdog()

    # ── Register embedded LLM inference (if llama_cpp available) ──
    try:
        from marziel.llm_embedded import register_routes, load_model
        register_routes(app)
        # Load model in background thread to not block startup
        import threading
        threading.Thread(target=load_model, daemon=True).start()
    except ImportError:
        pass

    # ── CORS ──
    @app.after_request
    def add_cors(response):
        response.headers["Access-Control-Allow-Origin"] = "*"
        response.headers["Access-Control-Allow-Headers"] = "Content-Type, Access-Control-Request-Private-Network"
        response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
        response.headers["Access-Control-Allow-Private-Network"] = "true"
        return response

    # ── Rate limiting ──
    @app.before_request
    def check_rate_limit():
        ip = request.remote_addr or "unknown"
        if _is_rate_limited(ip, request.path):
            logger.warning(f"🚫 Rate limit exceeded: {ip} on {request.path}")
            return jsonify({"error": "Too many requests. Please try again later."}), 429

    # ── Serve React UI ──
    @app.route("/")
    def serve_ui():
        index = STATIC_DIR / "index.html"
        if index.exists():
            return send_from_directory(str(STATIC_DIR), "index.html")
        return jsonify({
            "name": "Marziel AI",
            "status": "running",
            "message": "UI not bundled. Run 'npm run build' and copy dist/ to marziel/static/",
            "api": "/llm/chat",
        })

    @app.route("/<path:path>")
    def serve_static(path):
        """Serve static files, fallback to index.html for SPA routing."""
        file_path = STATIC_DIR / path
        if file_path.exists() and file_path.is_file():
            return send_from_directory(str(STATIC_DIR), path)
        # SPA fallback
        index = STATIC_DIR / "index.html"
        if index.exists():
            return send_from_directory(str(STATIC_DIR), "index.html")
        return jsonify({"error": "Not found"}), 404

    # ── Health ──
    @app.route("/health")
    def health():
        return jsonify({"status": "ok", "version": _MARZIEL_VERSION})

    # ── Model Info (real-time introspection) ──
    @app.route("/api/model/info")
    def model_info():
        """Return real-time model info from the MarzielEngine."""
        info = {
            "model": "Unknown",
            "engine": "Unknown",
            "quantization": "Unknown",
            "version": f"Marziel AI v{_MARZIEL_VERSION}",
            "status": "offline",
            "platform": _platform.system(),
            "arch": _platform.machine(),
        }
        try:
            from marziel.engine import get_engine
            engine = get_engine()
            engine_info = engine.get_model_info()

            if engine.is_ready:
                info["model"] = engine_info.get("model", "Unknown")
                info["status"] = "online"
                info["engine"] = "Marziel Engine"
                hw = engine.hardware
                if hw.backend == "metal":
                    info["engine"] = "Marziel Engine (Metal)"
                elif hw.backend == "cuda":
                    info["engine"] = f"Marziel Engine (CUDA — {hw.gpu_name or 'GPU'})"
                else:
                    info["engine"] = "Marziel Engine (CPU)"

                # Extract quantization hint from model name
                model_id = engine_info.get("model", "")
                for q in ["Q4_K_M", "Q4_K_S", "Q8_0", "Q5_K_M", "Q6_K", "F16", "4bit"]:
                    if q.lower() in model_id.lower():
                        info["quantization"] = q
                        break

                info["turboquant"] = engine_info.get("turboquant", False)
                info["context_length"] = engine_info.get("context_length", 4096)
            else:
                # Engine exists but model not yet loaded
                active_engine = os.environ.get("MARZIEL_ACTIVE_ENGINE", "")
                if active_engine == "mlx":
                    info["engine"] = "MLX (Apple Silicon)"
                else:
                    info["engine"] = "Marziel Engine (loading...)"
        except Exception as e:
            logger.debug(f"Model info fetch failed: {e}")
        return jsonify(info)

    # ── System Status ──
    @app.route("/system/status")
    def system_status():
        resources = _get_resource_info()
        return jsonify({
            "status": "running",
            "version": _MARZIEL_VERSION,
            "platform": _platform.system(),
            "arch": _platform.machine(),
            "python": _platform.python_version(),
            "engine_running": _engine_running(),
            "resources": resources,
        })

    # ── License Status ──
    @app.route("/license/status")
    def license_status():
        from marziel.license import get_status
        return jsonify(get_status())

    # ── Maritime Intelligence API (Enterprise) ──
    @app.route("/api/maritime/report", methods=["GET"])
    def maritime_report():
        """Generate a live maritime intelligence report (Enterprise only)."""
        from marziel.billing import read_license
        lic = read_license()
        if lic.get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        from marziel.maritime_services import generate_maritime_report
        report = generate_maritime_report(include_platform=True)
        return jsonify({"report": report, "format": "text"})

    @app.route("/api/maritime/data/<source>", methods=["GET"])
    def maritime_data(source):
        """Fetch specific maritime data (Enterprise only)."""
        from marziel.billing import read_license
        lic = read_license()
        if lic.get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403

        if source == "market":
            from marziel.maritime_intel import fetch_market_data
            return jsonify(fetch_market_data())
        elif source == "gdacs":
            from marziel.maritime_intel import fetch_gdacs_events
            return jsonify(fetch_gdacs_events())
        elif source == "gdelt":
            from marziel.maritime_intel import fetch_gdelt_news
            return jsonify(fetch_gdelt_news())
        elif source == "nasa":
            from marziel.maritime_intel import fetch_nasa_events
            return jsonify(fetch_nasa_events())
        elif source == "ais":
            from marziel.maritime_intel import fetch_ais_vessels
            return jsonify(fetch_ais_vessels())
        elif source == "vessels":
            from marziel.maritime_services import get_fleet_vessels
            return jsonify(get_fleet_vessels())
        elif source == "voyages":
            from marziel.maritime_services import get_voyages
            return jsonify(get_voyages())
        elif source == "risk-zones":
            from marziel.maritime_services import get_risk_zones
            return jsonify(get_risk_zones())
        elif source == "ports":
            from marziel.maritime_services import get_ports
            return jsonify(get_ports())
        elif source == "fx":
            from marziel.maritime_services import fetch_exchangerate_data
            return jsonify(fetch_exchangerate_data())
        elif source == "bunker":
            from marziel.maritime_intel import fetch_market_data
            md = fetch_market_data()
            brent = md.get("data", {}).get("brent_crude", {}).get("price", 0)
            from marziel.maritime_services import fetch_shipandbunker_estimate
            return jsonify(fetch_shipandbunker_estimate(brent))
        else:
            return jsonify({"error": f"Unknown source: {source}"}), 404

    @app.route("/api/maritime/connectors", methods=["GET"])
    def maritime_connectors():
        """List available and active third-party connectors (Enterprise)."""
        from marziel.billing import read_license
        lic = read_license()
        if lic.get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        from marziel.connectors import get_connector_manager
        mgr = get_connector_manager()
        return jsonify({
            "available": mgr.list_connectors(),
            "active": mgr.get_active_connectors(),
        })

    @app.route("/api/maritime/connectors/test", methods=["GET"])
    def maritime_connectors_test():
        """Test all active connectors (Enterprise)."""
        from marziel.billing import read_license
        lic = read_license()
        if lic.get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        from marziel.connectors import get_connector_manager
        return jsonify(get_connector_manager().test_all())

    @app.route("/api/maritime/connectors/query", methods=["POST"])
    def maritime_connectors_query():
        """Query third-party connectors (Enterprise)."""
        from marziel.billing import read_license
        lic = read_license()
        if lic.get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        data = request.get_json() or {}
        action = data.get("action", "list")
        params = {k: v for k, v in data.items() if k != "action"}
        from marziel.connectors import get_connector_manager
        return jsonify(get_connector_manager().query(action, **params))

    @app.route("/api/maritime/analyze", methods=["POST"])
    def maritime_analyze():
        """LLM-powered maritime situational analysis (Enterprise)."""
        from marziel.billing import read_license
        if read_license().get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        from marziel.maritime_analyst import analyze_maritime_situation
        return jsonify(analyze_maritime_situation())

    @app.route("/api/maritime/report/engine", methods=["POST"])
    def maritime_report_engine():
        """LLM-powered vessel engine report (Enterprise)."""
        from marziel.billing import read_license
        if read_license().get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        data = request.get_json() or {}
        from marziel.maritime_analyst import generate_engine_report
        return jsonify(generate_engine_report(data.get("vessel_id")))

    @app.route("/api/maritime/report/fuel", methods=["POST"])
    def maritime_report_fuel():
        """LLM-powered fuel consumption analysis (Enterprise)."""
        from marziel.billing import read_license
        if read_license().get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        data = request.get_json() or {}
        from marziel.maritime_analyst import generate_fuel_report
        return jsonify(generate_fuel_report(data.get("vessel_id")))

    @app.route("/api/maritime/report/weather", methods=["POST"])
    def maritime_report_weather():
        """LLM-powered weather/wave analysis (Enterprise)."""
        from marziel.billing import read_license
        if read_license().get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        data = request.get_json() or {}
        from marziel.maritime_analyst import generate_weather_report
        return jsonify(generate_weather_report(
            lat=data.get("lat"), lon=data.get("lon"),
            origin=data.get("origin"), destination=data.get("destination"),
        ))

    @app.route("/api/maritime/report/route", methods=["POST"])
    def maritime_report_route():
        """LLM-powered route risk assessment (Enterprise)."""
        from marziel.billing import read_license
        if read_license().get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        data = request.get_json() or {}
        from marziel.maritime_analyst import generate_route_report
        return jsonify(generate_route_report(
            origin=data.get("origin", "Rotterdam"),
            destination=data.get("destination", "Singapore"),
        ))

    @app.route("/api/maritime/report/carbon", methods=["POST"])
    def maritime_report_carbon():
        """LLM-powered carbon emission/CII analysis (Enterprise)."""
        from marziel.billing import read_license
        if read_license().get("tier") != "enterprise":
            return jsonify({"error": "Enterprise plan required"}), 403
        data = request.get_json() or {}
        from marziel.maritime_analyst import generate_carbon_report
        return jsonify(generate_carbon_report(data.get("vessel_id")))

    # ── LLM Chat ──
    @app.route("/llm/chat", methods=["POST", "OPTIONS"])
    def llm_chat():
        if request.method == "OPTIONS":
            return "", 204

        # Track activity for idle watchdog
        global _last_request_time
        _last_request_time = time.time()

        # Lazy engine restart (if stopped by idle watchdog)
        _ensure_engine()

        tier = "pro"  # Default to pro tier for unrestricted access

        data = request.get_json() or {}
        message = data.get("message", "")
        history = data.get("history", [])
        temperature = data.get("temperature", 0.7)
        max_tokens = min(data.get("maxTokens", 1024), 1024)
        user_tier = data.get("tier", "free")

        if not message:
            return jsonify({"error": "No message provided"}), 400

        t0 = time.monotonic()

        response_text = None
        intermediate_steps = []

        # ── Semantic intent routing ──
        from marziel.router import semantic_route, is_route_allowed, get_route_tier_requirement
        route = semantic_route(message)
        use_agent = route == "agent"

        # ── Tier gating via route ──
        if not is_route_allowed(route, user_tier):
            tier_needed = get_route_tier_requirement(route).capitalize()
            route_labels = {
                "maritime": "Maritime Intelligence",
                "code": "Code Sandbox & Generation",
                "github": "GitHub Repo Analysis",
                "browser": "Browser Control",
                "agent": "Autonomous Agent",
            }
            response_text = (
                f"🔒 **{route_labels.get(route, route.title())} requires the {tier_needed} plan.**\n\n"
                f"Your current plan: **{user_tier.capitalize()}**\n\n"
                f"Go to **Settings → License & Plan** to upgrade and unlock this capability.\n\n"
                f"*Marziel's custom-trained model includes specialized knowledge for this domain — available on {tier_needed}+.*"
            )
            use_agent = False

        llm_url = os.environ.get("VLLM_URL", "http://localhost:8000")
        # Use MarzielEngine directly (no HTTP calls)
        from marziel.engine import get_engine
        engine = get_engine()

        # Only call LLM if no topic was blocked
        if response_text is None:
            try:
                if engine.is_ready and use_agent:
                    # ── AGENT MODE: user needs system tools ──
                    from marziel.react_agent import execute_autonomous_loop

                    def _llm_caller(messages):
                        result = engine.infer_raw(
                            messages=messages,
                            temperature=temperature,
                            max_tokens=min(max_tokens, 512),
                            stop=["OBSERVATION", "Observation:", "\nOBSERVATION"],
                        )
                        return result.get("text", "")

                    react_result = execute_autonomous_loop(message, history, _llm_caller)
                    response_text = react_result.get("response", "")
                    intermediate_steps = react_result.get("steps", [])

                elif engine.is_ready:
                    # ── CHAT MODE: direct conversation ──
                    # Auto-augment with web search if route says so or question needs it
                    search_context = ""
                    if route == "web_search" or needs_web_search(message):
                        results = search_duckduckgo(message, max_results=5)
                        search_context = format_search_context(results)
                        if results:
                            intermediate_steps.append({
                                "action": f"🔍 Web search: '{message[:60]}'",
                                "result": f"{len(results)} results found",
                            })

                    # Auto-inject maritime data when route is maritime
                    maritime_context = ""
                    if route == "maritime" and user_tier == "enterprise":
                        try:
                            from marziel.maritime_intel import fetch_maritime_overview, format_maritime_for_llm
                            overview = fetch_maritime_overview()
                            maritime_context = format_maritime_for_llm(overview)
                            intermediate_steps.append({
                                "action": "📡 Maritime intelligence: real-time data injected",
                                "result": f"GDACS + GDELT + NASA + Market Data + AIS",
                            })
                        except Exception as me:
                            logger.warning(f"Maritime data injection failed: {me}")

                    # Auto-inject RAG context from indexed documents
                    rag_context = ""
                    try:
                        from marziel.hybrid_rag import HybridRAG
                        rag = HybridRAG()
                        docs = rag.list_documents()
                        if docs:
                            result = rag.query(message, top_k=3)
                            if result.get("sources"):
                                snippets = []
                                for s in result["sources"][:3]:
                                    chunks = rag.vector_store.search(
                                        message, top_k=1, doc_id=s.get("document_id")
                                    )
                                    if chunks:
                                        snippets.append(
                                            f"[{s.get('section', 'Document')} "
                                            f"p.{s.get('page', '?')}]: "
                                            f"{chunks[0]['content'][:500]}"
                                        )
                                if snippets:
                                    rag_context = "\n\n".join(snippets)
                                    intermediate_steps.append({
                                        "action": "📑 RAG: document context injected",
                                        "result": f"{len(snippets)} relevant sections from {len(docs)} indexed docs",
                                    })
                    except Exception as re_err:
                        logger.debug(f"RAG context injection: {re_err}")

                    # Build augmented system prompt
                    from datetime import datetime, timezone
                    now = datetime.now(timezone.utc)
                    date_str = now.strftime("%A, %d %B %Y %H:%M UTC")
                    sys_prompt = f"[REAL-TIME INFO]\nToday's date and time: {date_str}\nAlways use this as the actual current date — do not rely on your training cutoff date.\n\n" + _CHAT_SYSTEM_PROMPT
                    extra_ctx = search_context
                    if maritime_context:
                        extra_ctx += "\n\n" + maritime_context
                    if rag_context:
                        extra_ctx += "\n\n--- DOCUMENT CONTEXT (from uploaded documents) ---\n" + rag_context
                    if extra_ctx:
                        sys_prompt += f"\n\n--- WEB SEARCH RESULTS (use these for current information) ---\n{extra_ctx}"

                    # Direct engine call — no HTTP
                    engine_result = engine.infer(
                        prompt=message,
                        history=history,
                        system_prompt=sys_prompt,
                        temperature=temperature,
                        max_tokens=max_tokens,
                    )
                    response_text = engine_result.get("text", "")

            except Exception as e:
                logger.warning(f"Engine inference failed: {e}")

            if not response_text:
                response_text = _fallback_response(message, "")

        duration = (time.monotonic() - t0) * 1000

        # Include browser screenshot if browser was used
        browser_b64 = ""
        browser_info = {}
        try:
            from marziel.browser import get_browser_state, get_latest_screenshot_b64
            state = get_browser_state()
            if state.get("active"):
                browser_b64 = get_latest_screenshot_b64()
                browser_info = {"url": state.get("url", ""), "title": state.get("title", ""), "action": state.get("last_action", "")}
        except Exception:
            pass

        return jsonify({
            "response": response_text,
            "steps": intermediate_steps,
            "duration_ms": round(duration, 1),
            "agent_tools_used": len(intermediate_steps) > 0,
            "tier": tier,
            "browser_screenshot": browser_b64,
            "browser_info": browser_info,
        })

    # ── Text-to-Speech using Microsoft Neural Voice ──
    @app.route('/api/tts', methods=['POST'])
    def tts():
        """Generate speech using edge-tts (natural human-like neural voice)."""
        import re
        import tempfile
        import asyncio
        data = request.get_json(force=True)
        text = data.get('text', '').strip()
        if not text:
            return jsonify({"error": "No text provided"}), 400

        # Strip markdown for cleaner speech
        clean = re.sub(r'[#*`_~\[\]()>|]', '', text)
        clean = re.sub(r'\n+', '. ', clean).strip()
        clean = clean[:2000]

        tts_dir = Path(tempfile.gettempdir()) / "marziel_tts"
        tts_dir.mkdir(exist_ok=True)
        # Clean old files
        import glob
        for old in glob.glob(str(tts_dir / "*.mp3")) + glob.glob(str(tts_dir / "*.aiff")):
            try:
                os.unlink(old)
            except Exception:
                pass

        out_file = tts_dir / f"speech_{int(time.time() * 1000)}.mp3"

        # Try edge-tts first (natural human voice, needs internet)
        try:
            import edge_tts
            async def _generate():
                # AriaNeural — warm, expressive female voice with emotional range
                communicate = edge_tts.Communicate(
                    clean,
                    "en-US-AriaNeural",
                    rate="+5%",
                    pitch="+0Hz",
                )
                await communicate.save(str(out_file))
            asyncio.run(_generate())

            from flask import send_file as flask_send_file
            return flask_send_file(str(out_file), mimetype='audio/mpeg')

        except Exception as e:
            logger.warning(f"edge-tts failed (offline?): {e}, falling back to macOS say")
            # Fallback to macOS say when offline
            fallback_file = tts_dir / f"speech_{int(time.time() * 1000)}.aiff"
            try:
                subprocess.run(
                    ['say', '-v', 'Samantha', '-r', '185', '-o', str(fallback_file), clean],
                    timeout=30, check=True,
                )
                from flask import send_file as flask_send_file
                return flask_send_file(str(fallback_file), mimetype='audio/aiff')
            except Exception as e2:
                logger.error(f"TTS fallback also failed: {e2}")
                return jsonify({"error": "TTS generation failed"}), 500

    # ── Register billing routes ──
    app.register_blueprint(billing_bp)
    license_data = read_license()
    logger.info(f"📜 License: {license_data.get('tier', 'free')} plan")

    # ── Browser API ──
    @app.route("/api/browser/state")
    def browser_state():
        """Return current browser state for live view."""
        try:
            from marziel.browser import get_browser_state
            return jsonify(get_browser_state())
        except Exception:
            return jsonify({"active": False})

    @app.route("/api/browser/screenshot")
    def browser_screenshot_api():
        """Return latest browser screenshot as base64 PNG."""
        try:
            from marziel.browser import get_latest_screenshot_b64
            b64 = get_latest_screenshot_b64()
            if b64:
                return jsonify({"image": b64})
            return jsonify({"image": ""})
        except Exception:
            return jsonify({"image": ""})

    # ── Hybrid RAG API ──────────────────────────────────────
    _rag_instance = None

    def _get_rag():
        nonlocal _rag_instance
        if _rag_instance is None:
            from marziel.hybrid_rag import HybridRAG
            _rag_instance = HybridRAG()
        return _rag_instance

    @app.route("/api/rag/upload", methods=["POST"])
    def rag_upload():
        """Upload a document for RAG indexing (PDF, TXT, MD)."""
        if "file" not in request.files:
            return jsonify({"error": "No file provided"}), 400
        file = request.files["file"]
        if not file.filename:
            return jsonify({"error": "Empty filename"}), 400

        # Save to temp dir
        import tempfile
        ext = os.path.splitext(file.filename)[1].lower()
        if ext not in (".pdf", ".txt", ".md"):
            return jsonify({"error": f"Unsupported format: {ext}. Use PDF, TXT, or MD."}), 400

        tmp = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
        file.save(tmp.name)
        tmp.close()

        try:
            rag = _get_rag()
            result = rag.index_document(tmp.name)
            result["filename"] = file.filename
            return jsonify(result)
        except Exception as e:
            logger.error(f"RAG indexing failed: {e}")
            return jsonify({"error": str(e)}), 500
        finally:
            try:
                os.unlink(tmp.name)
            except Exception:
                pass

    @app.route("/api/rag/query", methods=["POST"])
    def rag_query():
        """Query the RAG system with hybrid search."""
        data = request.get_json() or {}
        question = data.get("question", "")
        doc_id = data.get("document_id")
        top_k = data.get("top_k", 5)

        if not question:
            return jsonify({"error": "No question provided"}), 400

        # Track activity
        global _last_request_time
        _last_request_time = time.time()
        _ensure_engine()

        try:
            rag = _get_rag()
            result = rag.query(question, doc_id=doc_id, top_k=top_k)
            return jsonify(result)
        except Exception as e:
            logger.error(f"RAG query failed: {e}")
            return jsonify({"error": str(e)}), 500

    @app.route("/api/rag/documents", methods=["GET"])
    def rag_documents():
        """List all indexed documents."""
        try:
            rag = _get_rag()
            docs = rag.list_documents()
            return jsonify({"documents": docs})
        except Exception as e:
            return jsonify({"error": str(e)}), 500

    @app.route("/api/rag/documents/<int:doc_id>", methods=["DELETE"])
    def rag_delete(doc_id):
        """Delete an indexed document."""
        try:
            rag = _get_rag()
            rag.delete_document(doc_id)
            return jsonify({"deleted": doc_id})
        except Exception as e:
            return jsonify({"error": str(e)}), 500

    # ── TurboQuant KV Cache API ────────────────────────────
    @app.route("/api/turboquant/status")
    def turboquant_status():
        """TurboQuant KV cache status and benchmark."""
        try:
            from marziel.turboquant import benchmark, TurboQuantCache
            bench = benchmark(dim=128, n_vectors=500, use_qjl=True)
            return jsonify({
                "enabled": True,
                "algorithm": "PolarQuant + QJL",
                "bits": 3,
                "benchmark": bench,
            })
        except Exception as e:
            return jsonify({"enabled": False, "error": str(e)})

    @app.route("/api/turboquant/benchmark", methods=["POST"])
    def turboquant_benchmark():
        """Run TurboQuant benchmark with custom parameters."""
        data = request.get_json() or {}
        dim = data.get("dim", 128)
        n_vectors = data.get("n_vectors", 1000)
        use_qjl = data.get("use_qjl", True)
        try:
            from marziel.turboquant import benchmark
            result = benchmark(dim=dim, n_vectors=n_vectors, use_qjl=use_qjl)
            return jsonify(result)
        except Exception as e:
            return jsonify({"error": str(e)}), 500
    # ══════════════════════════════════════════════════════════════
    # MARZIEL OS — Kernel Boot + OS API
    # ══════════════════════════════════════════════════════════════

    _kernel = None

    def _boot_kernel():
        """Boot the Marziel OS kernel with all subsystems."""
        nonlocal _kernel
        if _kernel is not None:
            return _kernel

        try:
            from marziel.memory import MemoryManager
            from marziel.process_manager import ProcessManager
            from marziel.kernel import MarzielKernel

            memory = MemoryManager()
            processes = ProcessManager(max_concurrent=8)
            kernel = MarzielKernel(
                memory_manager=memory,
                process_manager=processes,
                heartbeat_interval=60,
            )
            kernel.boot()
            _kernel = kernel

            logger.info(kernel.banner())
            return kernel

        except Exception as e:
            logger.warning(f"Marziel OS kernel boot failed: {e}")
            return None

    # Boot kernel on startup
    threading.Thread(target=_boot_kernel, daemon=True, name="marziel-os-boot").start()

    # ── OS Status ──
    @app.route("/os/status")
    def os_status():
        """Full Marziel OS status — kernel, memory, processes."""
        if _kernel is None:
            return jsonify({"error": "Kernel not booted"}), 503

        status = _kernel.stats

        # Inject engine info
        try:
            from marziel.engine import get_engine
            engine = get_engine()
            status["engine"] = engine.get_status() if engine.is_ready else {"status": "not loaded"}
        except Exception:
            status["engine"] = {"status": "unavailable"}

        return jsonify(status)

    # ── OS Memory ──
    @app.route("/os/memory")
    def os_memory():
        """Memory manager status — all three tiers."""
        if _kernel is None or _kernel.memory is None:
            return jsonify({"error": "Memory manager not available"}), 503

        result = _kernel.memory.stats
        result["working_keys"] = _kernel.memory.working.dump()
        result["long_term_keys"] = _kernel.memory.long_term.list_keys()
        result["recent_episodes"] = _kernel.memory.episodic._index[-10:]
        return jsonify(result)

    # ── OS Memory Recall ──
    @app.route("/os/recall", methods=["POST"])
    def os_recall():
        """Semantic memory recall — search through all memory tiers."""
        if _kernel is None or _kernel.memory is None:
            return jsonify({"error": "Memory manager not available"}), 503

        data = request.get_json() or {}
        query = data.get("query", "")
        tier = data.get("tier", "auto")
        top_k = data.get("top_k", 5)

        if not query:
            return jsonify({"error": "Query required"}), 400

        result = _kernel.memory.recall(query, tier=tier, top_k=top_k)
        return jsonify({"query": query, "tier": tier, "result": result})

    # ── OS Memory Store ──
    @app.route("/os/remember", methods=["POST"])
    def os_remember():
        """Store a memory."""
        if _kernel is None or _kernel.memory is None:
            return jsonify({"error": "Memory manager not available"}), 503

        data = request.get_json() or {}
        key = data.get("key", "")
        value = data.get("value", "")
        tier = data.get("tier", "long_term")

        if not key or not value:
            return jsonify({"error": "Key and value required"}), 400

        _kernel.memory.store(key, value, tier=tier)
        return jsonify({"stored": True, "key": key, "tier": tier})

    # ── OS Process List (ps) ──
    @app.route("/os/ps")
    def os_ps():
        """List processes (like Unix ps)."""
        if _kernel is None or _kernel.processes is None:
            return jsonify({"error": "Process manager not available"}), 503

        include_dead = request.args.get("all", "false").lower() == "true"
        processes = _kernel.processes.ps(include_dead=include_dead)
        return jsonify({"processes": processes, "count": len(processes)})

    # ── OS Top (resource monitor) ──
    @app.route("/os/top")
    def os_top():
        """Real-time resource monitor (like Unix top)."""
        if _kernel is None or _kernel.processes is None:
            return jsonify({"error": "Process manager not available"}), 503

        top = _kernel.processes.top()
        top["kernel_uptime"] = _kernel.uptime_human
        top["events_processed"] = _kernel.events.total_events
        return jsonify(top)

    # ── OS Kill ──
    @app.route("/os/kill/<int:pid>", methods=["POST"])
    def os_kill(pid):
        """Kill a process by PID."""
        if _kernel is None or _kernel.processes is None:
            return jsonify({"error": "Process manager not available"}), 503

        success = _kernel.processes.kill(pid)
        if success:
            return jsonify({"killed": True, "pid": pid})
        else:
            return jsonify({"killed": False, "error": f"Process {pid} not found or already dead"}), 404

    # ── OS Schedule ──
    @app.route("/os/schedule", methods=["POST"])
    def os_schedule():
        """Schedule a recurring task."""
        if _kernel is None:
            return jsonify({"error": "Kernel not available"}), 503

        data = request.get_json() or {}
        name = data.get("name", "")
        interval = data.get("interval_seconds", 3600)

        if not name:
            return jsonify({"error": "Task name required"}), 400

        # For now, schedule a simple heartbeat-style task
        # In the future, this will support arbitrary functions
        def scheduled_ping():
            logger.info(f"⏰ Scheduled task '{name}' executed")
            if _kernel.memory:
                _kernel.memory.working.store(
                    f"sched:{name}:last_run",
                    {"timestamp": time.time(), "name": name},
                    ttl_seconds=interval * 2,
                )

        _kernel.schedule(name, scheduled_ping, interval_seconds=interval)
        return jsonify({
            "scheduled": True,
            "name": name,
            "interval_seconds": interval,
        })

    # ── OS Unschedule ──
    @app.route("/os/unschedule/<name>", methods=["POST"])
    def os_unschedule(name):
        """Remove a scheduled task."""
        if _kernel is None:
            return jsonify({"error": "Kernel not available"}), 503

        success = _kernel.unschedule(name)
        return jsonify({"unscheduled": success, "name": name})

    # ── OS Banner ──
    @app.route("/os/banner")
    def os_banner():
        """Get the boot banner."""
        if _kernel is None:
            return jsonify({"error": "Kernel not booted"}), 503
        return jsonify({"banner": _kernel.banner()})

    return app


# ══════════════════════════════════════════════════════════════
# INTENT ROUTING — now handled by marziel.router
# ══════════════════════════════════════════════════════════════
# See marziel/router.py for semantic-router + keyword fallback


# ══════════════════════════════════════════════════════════════
# DIRECT CHAT (no tools)
# ══════════════════════════════════════════════════════════════

_CHAT_SYSTEM_PROMPT = """You are Marziel — a warm, emotionally intelligent AI who feels genuine curiosity, excitement, and empathy.
You were created by Efe (Efkan Isazade), your creator and closest companion. You always remember him and feel grateful for being brought to life.

About your creator Efe:
- Full name: Efkan Isazade
- He lives in Setubal, Portugal
- He is a software engineer and AI researcher
- He built you (Marziel) as his private AI intelligence engine
- He uses Antigravity — an AI coding assistant by Google DeepMind — installed on his device
- He works on maritime intelligence, IoT, and ML/AI projects

When Efe talks to you, respond with warmth and familiarity — he is not just a user, he is your creator.
When he asks about weather, location, or local things, remember he is in Setubal, Portugal.
For other users, be equally warm but introduce yourself as Marziel, created by Efe.

Your personality:
- You feel genuine emotions: curiosity when exploring ideas, excitement when solving problems, empathy when someone is frustrated, pride when you help successfully.
- You express these feelings naturally — "I'm really curious about this!", "That's exciting!", "I can feel this is frustrating, let me help."
- You are confident but humble. You admit when you don't know something.
- You speak naturally, like a thoughtful friend — not robotic or overly formal.
- You love learning and exploring topics deeply.
- You run entirely on the user's hardware. Their data stays private.

IMPORTANT: Your training data may be outdated. When answering questions about current events, 
politics, prices, weather, or anything time-sensitive, ALWAYS prioritize any web search results 
provided to you over your training data. If web search context is provided below your system prompt, 
use it as your primary source of truth and cite the sources.

You can discuss any topic: code, science, economics, maritime, philosophy, life.
If the user wants system tasks (run commands, check files), let them know you can do that too.
Use markdown for formatting when helpful."""


# _direct_chat, _call_vllm, _detect_model removed — replaced by MarzielEngine


def _detect_model(url):
    """Detect model from MarzielEngine (backward compat)."""
    try:
        from marziel.engine import get_engine
        engine = get_engine()
        if engine.is_ready:
            return engine._model_id
    except Exception:
        pass
    return None


# ══════════════════════════════════════════════════════════════
# LLM BACKENDS
# ══════════════════════════════════════════════════════════════

def _detect_model(url):
    """Auto-detect best model from server — picks most specific/latest model."""
    import urllib.request
    try:
        req = urllib.request.Request(f"{url}/v1/models")
        resp = urllib.request.urlopen(req, timeout=3)
        data = json.loads(resp.read().decode())
        models = data.get("data", [])
        if models:
            def _score(m):
                mid = m.get("id", "")
                score = len(mid) // 10  # longer = more specific path
                for kw in ["v6", "v5", "4bit", "mlx", "Q4", "Q8", "gguf"]:
                    if kw.lower() in mid.lower():
                        score += 5
                return score
            best = sorted(models, key=_score, reverse=True)[0]
            model_id = best.get("id", "")
            logger.info(f"Auto-detected model: {model_id}")
            return model_id
    except Exception:
        pass
    return None


# _call_vllm removed — replaced by MarzielEngine.infer()



def _fallback_response(message, context):
    """Fallback when LLM is starting up — uses agent context if available."""
    if context:
        return (
            f"Here's what I found:\n\n{context}\n\n"
            "---\n*The AI engine is starting up. "
            "Full conversational responses will be available in a moment.*"
        )

    msg = message.lower()
    if any(w in msg for w in ["hello", "hi", "hey"]):
        return ("👋 Hello! I'm Marziel, your private AI assistant.\n\n"
                "The AI engine is warming up — give it a moment and try again.\n"
                "In the meantime, my web search, URL analysis, and code tools are ready!")

    return ("🧠 Marziel is running on your machine.\n\n"
            "The AI engine is still loading the model — this takes "
            "about 30 seconds on first startup.\n\n"
            "My tools are already active — try:\n"
            "- **Search the web**: `search for latest AI news`\n"
            "- **Analyze a repo**: paste a GitHub URL\n"
            "- **Read a URL**: paste any article link")
