"""
doit-fm CLI — doitfm init / run / status / stop / doctor / update
"""
from __future__ import annotations
import asyncio, os, platform, subprocess, sys, time
from pathlib import Path

import click

def _ok(m):    click.echo(f"  ✓  {m}")
def _err(m):   click.echo(f"  ✗  {m}")
def _info(m):  click.echo(f"  →  {m}")
def _head(m):  click.echo(f"\n{'─'*54}\n  {m}\n{'─'*54}")
def _blank():  click.echo()

DEPS = [
    ("psutil",       "psutil"),
    ("click",        "click"),
    ("cryptography", "cryptography"),
]

def _has(name: str) -> bool:
    try: __import__(name); return True
    except ImportError: return False


@click.group(invoke_without_command=True)
@click.version_option(package_name="doit-fm", prog_name="doitfm")
@click.pass_context
def cli(ctx):
    """doit-fm — AI Telegram assistant that controls your computer.

    \b
    First time?   doitfm init
    Already set?  doitfm run
    Problems?     doitfm doctor
    """
    if ctx.invoked_subcommand is None:
        click.echo(
            "\n  doit-fm v3.1 — AI Telegram computer assistant\n\n"
            "  doitfm init    — guided setup (~3 min)\n"
            "  doitfm run     — start agent\n"
            "  doitfm status  — check if running\n"
            "  doitfm stop    — stop agent\n"
            "  doitfm doctor  — diagnose problems\n"
            "  doitfm update  — upgrade to latest\n"
        )


# ── doctor ────────────────────────────────────────────────────────────────────

@cli.command()
def doctor():
    """Diagnose your installation."""
    click.echo("\n🩺  doit-fm doctor\n")
    v = sys.version_info
    if v >= (3, 10): _ok(f"Python {v.major}.{v.minor}.{v.micro}")
    else:            _err(f"Python {v.major}.{v.minor} — need 3.10+")
    _blank()
    _info("Checking dependencies...")
    missing = [(i, p) for i, p in DEPS if not _has(i)]
    for imp, pkg in DEPS:
        (_ok if _has(imp) else _err)(f"{pkg}{'' if _has(imp) else '  ← MISSING'}")
    if missing:
        pkgs = [p for _, p in missing]
        _info(f"Installing: {', '.join(pkgs)}")
        r = subprocess.run(
            [sys.executable, "-m", "pip", "install"] + pkgs,
            capture_output=True, text=True)
        if r.returncode == 0: _ok(f"Installed ✓")
        else: _err(f"Install failed: {r.stderr[:200]}")
    _blank()
    _info("Checking config...")
    from doit_fm.store import get_config
    from doit_fm.config import BASE_DIR, DATA_DIR
    cfg = get_config()
    if cfg.exists():
        _ok(f"Configured: {cfg.get('ai_provider')} / {cfg.get('ai_model')}")
    else:
        _info("Not configured — run: doitfm init")
    for d, lbl in [(BASE_DIR,"Config dir"),(DATA_DIR,"Data dir")]:
        (_ok if d.exists() else _info)(f"{lbl}: {d}")
    _blank()
    _info("Checking internet...")
    import socket
    try:
        socket.setdefaulttimeout(5)
        socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("8.8.8.8", 53))
        _ok("Internet reachable")
    except Exception:
        _err("No internet connection")
    click.echo()


# ── init ──────────────────────────────────────────────────────────────────────

@cli.command()
@click.option("--reconfigure", "-r", is_flag=True, help="Overwrite existing config.")
def init(reconfigure):
    """Guided setup wizard."""
    asyncio.run(_init_wizard(reconfigure))


async def _init_wizard(reconfigure: bool = False):
    from doit_fm.config import AI_PROVIDERS, BASE_DIR, DATA_DIR, LOG_DIR, VERSION
    from doit_fm.store import get_config

    click.echo(f"""
╔══════════════════════════════════════════════════╗
║        doit-fm v{VERSION} — Setup Wizard            ║
║  AI assistant controlled via Telegram            ║
║                                                  ║
║  💻  Control your computer remotely              ║
║  📁  Files, apps, shell commands                 ║
║  🔊  Volume, screenshots, notifications          ║
║  🌐  Web search, weather, reminders              ║
╚══════════════════════════════════════════════════╝
""")

    cfg = get_config()
    if cfg.exists() and not reconfigure:
        _ok("Already configured.")
        _info("Start with: doitfm run  or re-run: doitfm init --reconfigure")
        return

    # ── Step 1: Dependencies ──────────────────────────────────────────────────
    _head("Step 1 / 4 — Dependencies")
    missing = [(i, p) for i, p in DEPS if not _has(i)]
    if missing:
        pkgs = [p for _, p in missing]
        _info(f"Installing: {', '.join(pkgs)}")
        r = subprocess.run(
            [sys.executable, "-m", "pip", "install"] + pkgs,
            capture_output=True, text=True)
        if r.returncode == 0: _ok("Installed ✓")
        else:
            _err(f"Install failed: {r.stderr[:200]}")
            if not click.confirm("Continue anyway?", default=False): return
    else:
        _ok("All dependencies installed ✓")

    # ── Step 2: AI Provider ───────────────────────────────────────────────────
    _head("Step 2 / 4 — AI Provider")
    click.echo()

    providers = list(AI_PROVIDERS.keys())
    for i, key in enumerate(providers, 1):
        info     = AI_PROVIDERS[key]
        has_free = any(m.get("free") for m in info.get("models", []))
        badge    = "  🆓 free tier" if has_free else ""
        if key == "ollama": badge = "  🔒 local & free"
        click.echo(f"  {i}.  {info['name']}{badge}")

    _blank()
    while True:
        choice = click.prompt("Pick a number", default="1")
        try:
            idx = int(choice) - 1
            if 0 <= idx < len(providers): break
        except ValueError: pass
        _err("Enter a valid number.")

    provider_key  = providers[idx]
    provider_info = AI_PROVIDERS[provider_key]
    base_url      = provider_info["base_url"]

    # Show models
    models = provider_info.get("models", [])
    _blank()
    click.echo(f"  Models available on {provider_info['name']}:\n")
    for i, m in enumerate(models, 1):
        tag = "🆓 free" if m.get("free") else "💳 paid"
        click.echo(f"  {i}.  {m['label']}  [{tag}]")
        click.echo(f"       ID: {m['id']}")

    _blank()
    while True:
        mc = click.prompt("Select model", default="1")
        try:
            mi = int(mc) - 1
            if 0 <= mi < len(models):
                model_id = models[mi]["id"]; break
        except ValueError: pass
        _err("Enter a valid number.")

    # API key
    if provider_key == "ollama":
        api_key = "ollama"
        _ok("Ollama runs locally — no API key needed")
    else:
        key_url = provider_info.get("key_url","")
        click.echo(f"\n  📌 Get your API key at:\n     {key_url}\n")
        click.echo("  Key is stored encrypted on this machine. Never uploaded.")
        click.echo("  Visible as you type (safe — same as GitHub/AWS CLIs).\n")
        while True:
            sys.stdout.write("  Paste your API key: ")
            sys.stdout.flush()
            try:
                api_key = sys.stdin.readline().rstrip("\r\n")
            except (EOFError, KeyboardInterrupt):
                api_key = ""
            if not api_key.strip():
                _err("Nothing received."); continue
            if len(api_key.strip()) < 8:
                _err(f"Only {len(api_key.strip())} chars — API keys are usually 40+ characters.")
                if click.confirm("Try again?", default=True): continue
                break
            api_key = api_key.strip()
            _ok(f"Key received — {len(api_key)} chars ✓"); break

    _info("Testing AI connection...")
    try:
        from doit_fm.ai import validate_api_key
        ok_ai, msg = await validate_api_key(provider_key, model_id, api_key, base_url)
        if ok_ai:
            _ok(f"AI connected: {msg}")
        else:
            _err(f"Connection test: {msg}")
            if not click.confirm("Continue anyway?", default=True): return
    except Exception as e:
        _err(f"Connection test error: {e}")
        if not click.confirm("Continue anyway?", default=True): return

    # ── Step 3: Telegram ──────────────────────────────────────────────────────
    _head("Step 3 / 4 — Telegram Bot")
    click.echo(
        "\n  You need two things from Telegram:\n\n"
        "  1. Bot token:\n"
        "     → Open Telegram → search @BotFather\n"
        "     → Send /newbot and follow the steps\n"
        "     → Copy the token (looks like: 1234567890:ABCdefGHIjkl...)\n\n"
        "  2. Your user ID:\n"
        "     → Open Telegram → search @userinfobot\n"
        "     → Send /start → copy the number shown\n"
    )

    while True:
        bot_token = click.prompt("  Bot token").strip()
        if not bot_token:
            _err("Empty."); continue
        if ":" not in bot_token or len(bot_token) < 20:
            _err("Tokens look like: 123456789:ABCdef…")
            if not click.confirm("Try again?", default=True): break
            continue
        _info("Validating token...")
        try:
            from doit_fm.bot import validate_bot_token
            ok_tok, info_tok = await validate_bot_token(bot_token)
            if ok_tok:
                _ok(f"Bot validated: {info_tok}"); break
            else:
                _err(f"Token problem: {info_tok}")
        except Exception as e:
            _err(f"Validation error: {e}")
        if not click.confirm("Try again?", default=True): break

    while True:
        uid_str = click.prompt("  Your Telegram user ID (numbers only)").strip()
        digits  = "".join(c for c in uid_str if c.isdigit())
        try:
            telegram_user_id = int(digits)
            if telegram_user_id < 10000:
                _err("Too small — IDs are at least 5 digits."); continue
            _ok(f"User ID: {telegram_user_id}"); break
        except ValueError:
            _err("Numbers only.")

    _info("Sending test message...")
    try:
        from doit_fm.bot import send_test_message
        ok_msg, msg_info = await send_test_message(bot_token, telegram_user_id)
        if ok_msg: _ok("Test message sent — check Telegram! 🎉")
        else:
            _err(f"Test failed: {msg_info}")
            if not click.confirm("Continue anyway?", default=True): return
    except Exception as e:
        _err(f"Test message error: {e}")

    # ── Step 4: Save ──────────────────────────────────────────────────────────
    _head("Step 4 / 4 — Saving Configuration")
    for d, lbl in [(BASE_DIR,"Config dir"),(DATA_DIR,"Data dir"),(LOG_DIR,"Logs dir")]:
        try:
            d.mkdir(parents=True, exist_ok=True)
            _ok(f"{lbl}: {d}")
        except Exception as e:
            _err(f"{lbl}: {e}")

    cfg.set("ai_provider",      provider_key)
    cfg.set("ai_model",         model_id)
    cfg.set("ai_api_key",       api_key)
    cfg.set("ai_base_url",      base_url)
    cfg.set("telegram_token",   bot_token)
    cfg.set("telegram_user_id", telegram_user_id)
    cfg.set("setup_complete",   True)
    _ok("Configuration saved (AES-256 encrypted) ✓")

    _blank()
    pname = provider_info['name'][:22]
    mid   = model_id[:22]
    click.echo(
        "╔══════════════════════════════════════════╗\n"
        "║        🎉  doit-fm is ready!             ║\n"
        "║                                          ║\n"
       f"║  AI:      {pname:<22}  ║\n"
       f"║  Model:   {mid:<22}  ║\n"
       f"║  User ID: {str(telegram_user_id):<22}  ║\n"
        "╚══════════════════════════════════════════╝\n\n"
        "Start:  doitfm run\n\n"
        "Then in Telegram try:\n"
        "  /health       — see your CPU & RAM\n"
        "  /screenshot   — capture your screen\n"
        "  /vol up       — turn volume up\n"
        "  /run ls -la   — run any command\n"
        "  'weather in Tokyo'  — just talk naturally!\n"
    )

    if click.confirm("Start doit-fm now?", default=True):
        await _run_agent()


# ── run ───────────────────────────────────────────────────────────────────────

@cli.command()
def run():
    """Start the doit-fm agent."""
    asyncio.run(_run_agent())


async def _run_agent():
    import logging
    from doit_fm.config import VERSION, LOCK_FILE, DATA_DIR
    from doit_fm.store import get_config
    from doit_fm.ai import AIEngine
    from doit_fm.bot import TelegramBot
    from doit_fm.tools import register_reminder_callback

    DATA_DIR.mkdir(parents=True, exist_ok=True)
    log_file = DATA_DIR / "doit.log"
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s  %(levelname)-7s  %(name)s  %(message)s",
        handlers=[
            logging.StreamHandler(),
            logging.FileHandler(log_file, encoding="utf-8"),
        ],
    )
    log = logging.getLogger("doit.main")

    cfg = get_config()
    if not cfg.exists():
        _err("Not configured. Run: doitfm init"); return

    # Lock file — prevent duplicate instances
    if LOCK_FILE.exists():
        try:
            pid = int(LOCK_FILE.read_text().strip())
            import psutil
            if psutil.pid_exists(pid):
                _err(f"Already running (PID {pid}). Stop with: doitfm stop"); return
        except Exception: pass
    LOCK_FILE.write_text(str(os.getpid()))

    click.echo(
        f"\n  🤖  doit-fm v{VERSION} starting\n"
        f"  AI:       {cfg.get('ai_provider')} / {cfg.get('ai_model')}\n"
        f"  User ID:  {cfg.get('telegram_user_id')}\n"
        f"  Log:      {log_file}\n\n"
        f"  Press Ctrl+C to stop.\n"
    )

    ai  = AIEngine(cfg.all())
    bot = TelegramBot(
        token=cfg.get("telegram_token"),
        user_id=cfg.get("telegram_user_id"),
        ai_engine=ai,
    )

    # Wire reminder callbacks → Telegram
    register_reminder_callback(bot.send_message)

    async def startup():
        await asyncio.sleep(3)
        try:
            await bot.send_message(
                f"🤖 *doit-fm v{VERSION} is online!*\n\n"
                "Type /help to see all commands, or just talk naturally:\n"
                "  • _how's my computer doing?_\n"
                "  • _take a screenshot_\n"
                "  • _turn up the volume_\n"
                "  • _open Chrome_\n"
                "  • _weather in Paris_"
            )
        except Exception as e:
            log.warning("Startup message failed: %s", e)

    try:
        asyncio.create_task(startup())
        await bot.start()
    except KeyboardInterrupt:
        pass
    finally:
        try: LOCK_FILE.unlink(missing_ok=True)
        except Exception: pass
        log.info("doit-fm stopped.")
        click.echo("\n  Stopped.")


# ── status ────────────────────────────────────────────────────────────────────

@cli.command()
def status():
    """Is doit-fm running?"""
    from doit_fm.config import LOCK_FILE
    if LOCK_FILE.exists():
        try:
            pid = int(LOCK_FILE.read_text().strip())
            import psutil
            if psutil.pid_exists(pid):
                click.echo(f"🟢  doit-fm is running  (PID {pid})"); return
        except Exception: pass
    click.echo("🔴  doit-fm is not running")
    _info("Start it:  doitfm run")


# ── stop ──────────────────────────────────────────────────────────────────────

@cli.command()
def stop():
    """Stop the running agent."""
    import signal
    from doit_fm.config import LOCK_FILE
    if not LOCK_FILE.exists():
        _info("Not running."); return
    try:
        pid = int(LOCK_FILE.read_text().strip())
        os.kill(pid, signal.SIGTERM)
        time.sleep(1)
        LOCK_FILE.unlink(missing_ok=True)
        _ok(f"Stopped PID {pid}")
    except ProcessLookupError:
        LOCK_FILE.unlink(missing_ok=True)
        _info("Process not found — removed stale lock file.")
    except Exception as e:
        _err(f"Stop failed: {e}")


# ── update ────────────────────────────────────────────────────────────────────

@cli.command()
@click.option("--yes", "-y", is_flag=True)
def update(yes):
    """Check for updates and upgrade."""
    asyncio.run(_do_update(yes))


async def _do_update(auto: bool = False):
    import urllib.request, json
    from doit_fm.config import VERSION as current
    _info("Checking PyPI...")
    try:
        loop = asyncio.get_event_loop()
        resp = await loop.run_in_executor(None, lambda: urllib.request.urlopen(
            "https://pypi.org/pypi/doit-fm/json", timeout=10))
        data   = json.loads(resp.read())
        latest = data["info"]["version"]
        click.echo(f"  Current: {current}  →  Latest: {latest}")
        if latest == current:
            _ok("Already up to date!"); return
        if auto or click.confirm(f"Update to v{latest}?", default=True):
            r = subprocess.run(
                [sys.executable, "-m", "pip", "install", "--upgrade", "doit-fm"],
                capture_output=True, text=True)
            if r.returncode == 0:
                _ok(f"Updated to v{latest}!")
                _info("Restart:  doitfm stop && doitfm run")
            else:
                _err(f"Update failed: {r.stderr[:200]}")
    except Exception as e:
        _err(f"Update check failed: {e}")
        _info("Manual:  pip install --upgrade doit-fm")


if __name__ == "__main__":
    cli()
