"""Command-line entrypoint for RabbitHole.

Configuration is loaded from ~/.config/rabbithole/rabbithole.conf.
On first run, a template config file is created and the program exits.

Supports:
- Legacy single-provider mode (OPENROUTER_API_KEY)
- Multi-provider mode with automatic load balancing (OPENROUTER_API_KEYS, GROQ_API_KEYS, etc.)

Only a single positional topic argument is accepted; all other settings are loaded from the config file.
"""

import asyncio
import os
import shutil
import signal
import sys
import tempfile
from pathlib import Path

from .datastore import Datastore
from .llm import LLM
from .web_search import WebSearchConnector
from .orchestrator import Orchestrator
from .logger import log


# Default config file location
CONFIG_DIR = Path.home() / ".config" / "rabbithole"
CONFIG_FILE = CONFIG_DIR / "rabbithole.conf"

# Template config (embedded from .env.example)
CONFIG_TEMPLATE = """\
# RabbitHole configuration
# Edit this file with your API keys and preferences.
# Documentation: https://github.com/your-username/RabbitHole

# =============================================================================
# LLM PROVIDERS (at least one required)
# =============================================================================
# Get your API key at: https://openrouter.ai/keys

OPENROUTER_API_KEYS=your_openrouter_api_key_here
OPENROUTER_API_BASE=https://openrouter.ai/api
OPENROUTER_MODELS=stepfun/step-3.5-flash:free
OPENROUTER_BACKUP_MODEL=arcee-ai/trinity-large-preview:free
OPENROUTER_TASKS=all
OPENROUTER_LOG=1

# --- Groq (fast inference) ---
# GROQ_API_KEYS=your_groq_api_key_here
# GROQ_MODELS=moonshotai/kimi-k2-instruct-0905
# GROQ_BACKUP_MODEL=llama-3.3-70b-versatile
# GROQ_TASKS=report
# GROQ_LOG=1

# --- Google AI Studio ---
# GOOGLE_AI_KEYS=your_google_ai_key_here
# GOOGLE_AI_MODELS=gemini-2.0-flash-exp

# --- OpenAI (direct API) ---
# OPENAI_API_KEYS=your_openai_api_key_here
# OPENAI_MODELS=gpt-4o-mini

# --- Ollama (local models) ---
# OLLAMA_BASE_URLS=http://localhost:11434
# OLLAMA_MODELS=llama3.1:70b

# Provider fallback order
LLM_FALLBACK_CHAIN=openrouter,groq,google_ai,openai,ollama

# =============================================================================
# RESEARCH SETTINGS
# =============================================================================

# Research depth (2-10, higher = more thorough but slower)
MAX_DEPTH=3
MAX_CHILDREN=4

# Parallelism (1 = sequential, higher = faster but more memory)
MAX_CONCURRENT_TASKS=35
CONCURRENCY=30

# Output
OUTPUT_PATH=research_report.md
SUMMARY_WORD_COUNT=600
REPORT_MIN_WORDS=2000
REPORT_MAX_WORDS=8000

# =============================================================================
# WEB SEARCH
# =============================================================================
# Available: bing (free), brave, serpapi, tavily, exa, wikipedia, arxiv

SEARCH_PROVIDER=bing
SEARCH_FALLBACK_CHAIN=wikipedia,arxiv
WEB_SEARCH_DELAY_SEC=1.0

# API keys for paid search (only if using that provider)
# BRAVE_API_KEY=your_brave_api_key_here
# TAVILY_API_KEY=your_tavily_api_key_here

# =============================================================================
# RUNTIME
# =============================================================================

AUTO_CLEANUP=1
DB_PATH=state.db
PROGRESS_DISPLAY_MODE=tui
"""


def _get_config_path() -> Path:
    """Get config file path, respecting RABBITHOLE_CONFIG env var."""
    if os.environ.get("RABBITHOLE_CONFIG"):
        return Path(os.environ["RABBITHOLE_CONFIG"])
    return CONFIG_FILE


def _ensure_config() -> bool:
    """Ensure config file exists and is configured. Returns True if ready, False otherwise.
    
    Config precedence:
    1. RABBITHOLE_CONFIG env var (explicit path)
    2. ~/.config/rabbithole/rabbithole.conf (standard location)
    
    For development, set RABBITHOLE_CONFIG=.env to use a local file.
    """
    config_path = _get_config_path()
    
    if not config_path.exists():
        # Create config directory and file
        config_path.parent.mkdir(parents=True, exist_ok=True)
        config_path.write_text(CONFIG_TEMPLATE, encoding="utf-8")
        
        print(f"""
\033[1;36m🐰 RabbitHole - First Run Setup\033[0m

Created configuration file at:
  \033[1m{config_path}\033[0m

\033[1;33mNext steps:\033[0m
  1. Open the config file and add your API key:
     \033[2m$ nano {config_path}\033[0m

  2. Get an OpenRouter API key (free tier available):
     \033[2mhttps://openrouter.ai/keys\033[0m

  3. Run rabbithole again:
     \033[2m$ rabbithole "Your research topic"\033[0m

""")
        return False
    
    # Config exists - check if it has real API keys (not placeholders)
    content = config_path.read_text(encoding="utf-8")
    placeholder_keys = [
        "your_openrouter_api_key_here",
        "your_groq_api_key_here",
        "your_google_ai_key_here",
        "your_openai_api_key_here",
    ]
    
    # Check if at least one provider has a real key configured
    has_real_key = False
    for line in content.splitlines():
        line = line.strip()
        if line.startswith("#") or "=" not in line:
            continue
        key, value = line.split("=", 1)
        key = key.strip()
        value = value.strip().strip('"').strip("'")
        
        # Check for API key settings
        if key in ("OPENROUTER_API_KEYS", "GROQ_API_KEYS", "GOOGLE_AI_KEYS", "OPENAI_API_KEYS", "OLLAMA_BASE_URLS"):
            if value and value not in placeholder_keys and not value.startswith("your_"):
                has_real_key = True
                break
    
    if not has_real_key:
        print(f"""
\033[1;33m🐰 RabbitHole - Configuration Required\033[0m

Your config file exists but has no API keys configured:
  \033[1m{config_path}\033[0m

\033[1;33mTo fix:\033[0m
  1. Edit the config file and add your API key:
     \033[2m$ nano {config_path}\033[0m

  2. Get an OpenRouter API key (free tier available):
     \033[2mhttps://openrouter.ai/keys\033[0m

  3. Run rabbithole again:
     \033[2m$ rabbithole "Your research topic"\033[0m

""")
        return False
    
    return True


def _get_runtime_dir() -> str:
    """Get the runtime directory for transient files.
    
    Uses RUNTIME_DIR env var if set, otherwise uses system temp directory.
    For packaging (pipx/homebrew), this ensures no files are written to the project.
    """
    if os.environ.get("RUNTIME_DIR"):
        return os.environ["RUNTIME_DIR"]
    # Use system temp directory for clean packaging
    base_tmp = os.environ.get("TMPDIR", tempfile.gettempdir())
    runtime_dir = os.path.join(base_tmp, "rabbithole")
    os.makedirs(runtime_dir, exist_ok=True)
    return runtime_dir


def _cleanup_runtime(runtime_dir: str, keep_report: bool = True):
    """Clean up all runtime artifacts (db, cache, artifacts) after job completion.
    
    Args:
        runtime_dir: The runtime directory to clean
        keep_report: If True, don't delete anything (report is kept at OUTPUT_PATH)
    """
    # Clear in-memory caches
    try:
        from .llm import _summary_cache
        _summary_cache.clear()
    except Exception:
        pass
    try:
        from .web_search import _url_cache
        _url_cache.clear()
    except Exception:
        pass
    
    # Delete runtime directory contents (db, artifacts, logs)
    if runtime_dir and os.path.isdir(runtime_dir):
        try:
            # Only delete if it's in a temp location (safety check)
            base_tmp = os.environ.get("TMPDIR", tempfile.gettempdir())
            if runtime_dir.startswith(base_tmp) or runtime_dir.startswith("/tmp"):
                shutil.rmtree(runtime_dir, ignore_errors=True)
                log.progress("[cleanup] Runtime directory cleared")
            else:
                # For custom RUNTIME_DIR, just clear contents but keep the dir
                for item in os.listdir(runtime_dir):
                    item_path = os.path.join(runtime_dir, item)
                    if os.path.isdir(item_path):
                        shutil.rmtree(item_path, ignore_errors=True)
                    else:
                        os.remove(item_path)
                log.progress("[cleanup] Runtime contents cleared")
        except Exception as e:
            log.warning(f"Cleanup warning: {e}")


def _handle_interrupt(signum, frame):
    """Handle Ctrl+C cleanly - immediate exit with clean message."""
    # Finalize TUI mode if active (print newline after progress bar)
    log.finalize_tui()
    print("\n\033[33m⚡ Task cancelled\033[0m")
    # Use os._exit to forcefully terminate without waiting for threads
    os._exit(0)


# Install signal handler early
signal.signal(signal.SIGINT, _handle_interrupt)


def _load_dotenv(path: str = ".env"):
    if not os.path.exists(path):
        return
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            if "=" not in line:
                continue
            k, v = line.split("=", 1)
            k = k.strip()
            v = v.strip()
            # Handle inline comments (e.g., "value # comment")
            if "#" in v:
                v = v.split("#")[0].strip()
            if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")):
                v = v[1:-1]
            # do not override existing env vars
            if k not in os.environ:
                os.environ[k] = v


async def main_async(topic: str):
    # Load config from the designated config file
    config_path = _get_config_path()
    if config_path.exists():
        _load_dotenv(str(config_path))
    
    # Set up runtime directory (uses /tmp/rabbithole by default for clean packaging)
    runtime_dir = _get_runtime_dir()
    os.environ["RUNTIME_DIR"] = runtime_dir  # Propagate to datastore
    
    # Check if auto-cleanup is enabled (default: True)
    auto_cleanup = os.environ.get("AUTO_CLEANUP", "1").lower() not in ("0", "false", "no")
    
    db_path = os.environ.get("DB_PATH", "state.db")  # Relative path goes into runtime_dir
    ds = Datastore(db_path)
    ds.init()

    # Initialize executor limiter and log concurrency settings
    from .executor_limiter import get_max_tasks
    max_tasks = get_max_tasks()
    log.config(f"MAX_CONCURRENT_TASKS={max_tasks} (controls system-wide parallelism)")
    log.config(f"RUNTIME_DIR={runtime_dir}")

    # Initialize LLM (auto-detects single vs multi-provider mode)
    llm = LLM(provider="openrouter")
    
    # Log provider configuration
    if llm.use_multi_provider:
        log.config(f"Multi-provider mode enabled with {len(llm.registry)} provider instances")
        health = llm.get_provider_health()
        for pid, status in health.items():
            log.config(f"  - {pid}: {status}")
    elif llm.use_openrouter:
        log.config("Legacy mode: OpenRouter provider")
    elif llm.use_openai:
        log.config("Legacy mode: OpenAI provider")
    else:
        log.warning("No LLM provider configured - will use local fallbacks")

    connector = WebSearchConnector()
    log.config("connector=web_search")

    # optional lightweight embeddings for local retrieval
    try:
        from .embeddings import Embeddings

        embeddings = Embeddings()
    except Exception:
        embeddings = None

    concurrency = int(os.environ.get("CONCURRENCY", "1"))
    max_depth = int(os.environ.get("MAX_DEPTH", "2"))
    max_children = int(os.environ.get("MAX_CHILDREN", "2"))
    output = os.environ.get("OUTPUT_PATH", "research_report.md")

    orch = Orchestrator(datastore=ds, llm=llm, connector=connector, concurrency=concurrency, embeddings=embeddings)
    out = await orch.run_job(topic=topic, max_depth=max_depth, max_children=max_children, output_path=output, job_id=None)
    log.success(f"Report written to {out}")
    if getattr(orch, "job_id", None):
        print("Job ID:", orch.job_id)
    
    # Auto-cleanup runtime artifacts after successful completion
    if auto_cleanup:
        # Close database connection first
        try:
            if ds.conn:
                ds.conn.close()
        except Exception:
            pass
        _cleanup_runtime(runtime_dir)
        log.success("Runtime cleanup complete (db, cache, artifacts cleared)")


def main():
    # Simple CLI: rabbithole "topic" [--no-cleanup]
    args = sys.argv[1:]
    no_cleanup = "--no-cleanup" in args
    if no_cleanup:
        args.remove("--no-cleanup")
        os.environ["AUTO_CLEANUP"] = "0"
    
    # Ensure config exists (creates template on first run and exits)
    if not _ensure_config():
        sys.exit(0)
    
    topic = args[0] if args else "Sample topic: impacts of AI on labor"
    asyncio.run(main_async(topic))


if __name__ == "__main__":
    main()
