"""
BizNode Bot Runner — Multi-Bot Watchdog
=========================================
Runs run_bot.py directly (not via Docker) so the bot can reach
localhost Ollama and Qdrant. Auto-restarts on crash.

Supports multiple bots (Pro tier only):
    python bot_runner.py                # 1 bot (all tiers)
    python bot_runner.py --count 3      # 3 bots (Pro tier, reads BOT_TOKEN_1..3)

Reads BOT_TOKEN from .env or environment.
For multi-bot, set BOT_TOKEN_1, BOT_TOKEN_2, etc in .env.
Logs bot output to logs/bot.log (or logs/bot_N.log for multi-bot).

Stop: Ctrl+C
"""

from __future__ import annotations

import argparse
import os
import subprocess
import sys
import time
from pathlib import Path
from dotenv import load_dotenv

_NO_WINDOW = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
_HERE = Path(__file__).parent.resolve()
_BOT_SCRIPT = _HERE / "docker" / "bot" / "run_bot.py"
_LOG_DIR    = _HERE / "logs"
_LOG_DIR.mkdir(exist_ok=True)

MAX_CRASHES    = 20   # stop after this many consecutive crashes
RESTART_DELAY  = 5    # seconds between restarts

# USB-removed exit code — do not restart
USB_REMOVED_EXIT_CODE = 77

# Load .env from the project root
load_dotenv(dotenv_path=_HERE / ".env", override=False)


def _get_bot_tokens(count: int) -> list[str]:
    """
    Get bot tokens for N bots.
    For count=1: uses BOT_TOKEN or TELEGRAM_BOT_TOKEN
    For count>1: uses BOT_TOKEN_1, BOT_TOKEN_2, ... (falls back to BOT_TOKEN for slot 1)
    """
    tokens = []
    for i in range(1, count + 1):
        if count == 1:
            token = os.getenv("BOT_TOKEN") or os.getenv("TELEGRAM_BOT_TOKEN", "")
        else:
            token = os.getenv(f"BOT_TOKEN_{i}", "")
            if i == 1 and not token:
                token = os.getenv("BOT_TOKEN") or os.getenv("TELEGRAM_BOT_TOKEN", "")
        tokens.append(token)
    return tokens


def _make_bot_env(token: str, bot_index: int = 1) -> dict:
    """Create environment dict for a bot subprocess.

    For bot_index > 1, reads per-bot overrides from .env:
        BOT_NAME_{i}, BOT_BUSINESS_{i}, BOT_COLLECTION_{i},
        BOT_DB_TYPE_{i}, BOT_DB_NAME_{i}, BOT_LLM_MODEL_{i},
        BOT_INSTRUCTION_{i}
    """
    i = bot_index
    env = {
        **os.environ,
        "BOT_TOKEN":           token,
        "DASHBOARD_URL":       "http://localhost:7777",
        "OWNER_TELEGRAM_ID":   os.getenv("OWNER_TELEGRAM_ID", ""),
        "OLLAMA_URL":          os.getenv("OLLAMA_URL",       "http://localhost:11434/api/generate"),
        "OLLAMA_EMBED_URL":    os.getenv("OLLAMA_EMBED_URL", "http://localhost:11434/api/embeddings"),
        "QDRANT_URL":          os.getenv("QDRANT_URL",       "http://localhost:6333"),
        "OLLAMA_MODEL":        os.getenv("OLLAMA_MODEL",     "qwen3.5:4b"),
        "EMBEDDING_MODEL":     os.getenv("EMBEDDING_MODEL",  "nomic-embed-text"),
        "BOT_SECRET":          os.getenv("BOT_SECRET",       "biznode-bot-internal-2026"),
        "BOT_LOG_DIR":         str(_LOG_DIR),
        "BOT_INDEX":           str(i),
    }

    # Per-bot overrides (Pro multi-bot)
    if i > 1:
        bot_name = os.getenv(f"BOT_NAME_{i}", "")
        if bot_name:
            env["BOT_NAME"] = bot_name
        bot_biz = os.getenv(f"BOT_BUSINESS_{i}", "")
        if bot_biz:
            env["BOT_BUSINESS_NAME"] = bot_biz
        bot_collection = os.getenv(f"BOT_COLLECTION_{i}", f"biznode_memory_{i}")
        env["QDRANT_COLLECTION"] = bot_collection
        bot_model = os.getenv(f"BOT_LLM_MODEL_{i}", "")
        if bot_model:
            env["OLLAMA_MODEL"] = bot_model
        bot_instruction = os.getenv(f"BOT_INSTRUCTION_{i}", "")
        if bot_instruction:
            env["BOT_SYSTEM_INSTRUCTION"] = bot_instruction
        bot_db_type = os.getenv(f"BOT_DB_TYPE_{i}", "")
        if bot_db_type:
            env["DB_TYPE"] = bot_db_type
        bot_db_name = os.getenv(f"BOT_DB_NAME_{i}", "")
        if bot_db_name:
            env["DB_NAME"] = bot_db_name

    return env


# ---------------------------------------------------------------------------
# Single bot runner (original logic)
# ---------------------------------------------------------------------------
def run_single(token: str, bot_index: int = 1):
    """Run a single bot with auto-restart."""
    crashes = 0
    label = f"bot-{bot_index}" if bot_index > 1 else "bot"
    env = _make_bot_env(token, bot_index)

    print(f"[{label}] Starting (script: {_BOT_SCRIPT})")
    print(f"[{label}] Dashboard: {env['DASHBOARD_URL']}")
    print(f"[{label}] Logs: {_LOG_DIR}/bot.log")

    while True:
        start_time = time.time()
        try:
            proc = subprocess.run(
                [sys.executable, str(_BOT_SCRIPT)],
                cwd=str(_HERE / "docker" / "bot"),
                env=env,
                creationflags=_NO_WINDOW,
            )
            exit_code   = proc.returncode
            elapsed     = time.time() - start_time

        except KeyboardInterrupt:
            print(f"\n[{label}] Stopped by user.")
            break
        except Exception as exc:
            print(f"[{label}] Failed to start bot: {exc}")
            exit_code = -1
            elapsed   = time.time() - start_time

        # USB removed — stop permanently
        if exit_code == USB_REMOVED_EXIT_CODE:
            print(f"[{label}] USB license removed — not restarting.")
            print(f"[{label}] Insert your USB drive and run start.bat again.")
            break

        # If it ran for more than 60 seconds, treat as successful run (reset crash count)
        if elapsed > 60:
            crashes = 0
        else:
            crashes += 1

        if exit_code == 0:
            print(f"[{label}] Bot exited cleanly. Restarting...")
        else:
            print(
                f"[{label}] Bot crashed (exit={exit_code}, uptime={elapsed:.0f}s, "
                f"consecutive crashes={crashes}/{MAX_CRASHES})."
            )

        if crashes >= MAX_CRASHES:
            print(
                f"[{label}] {MAX_CRASHES} consecutive crashes — stopping to prevent spam. "
                "Fix the error and restart bot_runner.py manually."
            )
            break

        try:
            print(f"[{label}] Restarting in {RESTART_DELAY}s ...")
            time.sleep(RESTART_DELAY)
        except KeyboardInterrupt:
            print(f"\n[{label}] Stopped by user.")
            break


# ---------------------------------------------------------------------------
# Multi-bot runner
# ---------------------------------------------------------------------------
def run_multi(tokens: list[str]):
    """Run multiple bots as separate subprocesses, monitor all."""
    import threading

    threads = []
    for i, token in enumerate(tokens, 1):
        if not token:
            print(f"[bot-{i}] WARNING: No token set (BOT_TOKEN_{i}). Skipping.")
            continue
        t = threading.Thread(
            target=run_single,
            args=(token, i),
            name=f"bot-{i}",
            daemon=True,
        )
        threads.append(t)
        t.start()
        time.sleep(2)  # stagger starts

    if not threads:
        print("[bot_runner] No bots started — no valid tokens found.")
        return

    print(f"\n[bot_runner] {len(threads)} bot(s) running. Press Ctrl+C to stop all.\n")

    try:
        while any(t.is_alive() for t in threads):
            time.sleep(1)
    except KeyboardInterrupt:
        print("\n[bot_runner] Stopping all bots...")


# ---------------------------------------------------------------------------
# Duplicate detection
# ---------------------------------------------------------------------------
def _kill_existing_bots() -> int:
    """Kill any existing bot_runner.py and run_bot.py processes (except self). Returns count killed."""
    if sys.platform != "win32":
        return 0
    my_pid = os.getpid()
    killed = 0
    try:
        result = subprocess.run(
            ["powershell", "-NoProfile", "-Command",
             "Get-CimInstance Win32_Process -Filter \"name='pythonw.exe' or name='python.exe'\" "
             "| Select-Object ProcessId, CommandLine | ConvertTo-Json"],
            capture_output=True, text=True, creationflags=_NO_WINDOW
        )
        import json
        procs = json.loads(result.stdout) if result.stdout.strip() else []
        if isinstance(procs, dict):
            procs = [procs]
        for p in procs:
            pid = p.get("ProcessId", 0)
            cmd = p.get("CommandLine", "") or ""
            if pid == my_pid:
                continue
            if "bot_runner.py" in cmd or "run_bot.py" in cmd:
                try:
                    subprocess.run(["taskkill", "/F", "/PID", str(pid)],
                                   capture_output=True, creationflags=_NO_WINDOW)
                    killed += 1
                    print(f"[bot_runner] Killed stale process PID {pid}")
                except Exception:
                    pass
    except Exception as e:
        print(f"[bot_runner] Duplicate check failed: {e}")
    return killed


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main():
    parser = argparse.ArgumentParser(description="BizNode Bot Runner")
    parser.add_argument("--count", type=int, default=1, help="Number of bots to run (Pro: up to 5)")
    args = parser.parse_args()

    # Kill any existing bot processes before starting
    killed = _kill_existing_bots()
    if killed:
        print(f"[bot_runner] Cleaned up {killed} stale bot process(es). Waiting 3s...")
        time.sleep(3)

    bot_count = args.count

    # Enforce tier limits
    if bot_count > 1:
        try:
            sys.path.insert(0, str(_HERE))
            from services.tier_manager import get_max_bots, get_current_tier, TierLevel
            tier = get_current_tier()
            max_bots = get_max_bots()

            if tier < TierLevel.PRO:
                print(f"[bot_runner] Multi-bot requires Pro tier (current: {tier.name}).")
                print(f"[bot_runner] Upgrade at https://biznode.1bz.biz")
                print(f"[bot_runner] Starting 1 bot only.")
                bot_count = 1
            elif bot_count > max_bots:
                print(f"[bot_runner] Capping at {max_bots} bots (Pro tier limit).")
                bot_count = max_bots
        except Exception as e:
            print(f"[bot_runner] Tier check failed ({e}). Starting 1 bot.")
            bot_count = 1

    tokens = _get_bot_tokens(bot_count)

    # Validate at least 1 token
    if not tokens or not tokens[0]:
        print("[ERROR] BOT_TOKEN is not set. Add it to .env and retry.")
        sys.exit(1)

    if bot_count == 1:
        print("[bot_runner] Press Ctrl+C to stop.\n")
        run_single(tokens[0])
    else:
        run_multi(tokens)


if __name__ == "__main__":
    main()
