"""
cli.py — Command-line interface for doit-fm.

Commands:
  doit-fm init    — Interactive setup wizard
  doit-fm run     — Start the background agent
  doit-fm stop    — Stop a running agent
  doit-fm status  — Check if agent is running
  doit-fm doctor  — Diagnose the installation
"""
from __future__ import annotations

import asyncio
import os
import signal
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path

import click

from .config import AI_PROVIDERS, DATA_DIR, LOCK_FILE, LOG_DIR, STARTUP_DELAY
from .store import get_store


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _print_step(n: int, total: int, text: str) -> None:
    click.echo(click.style(f"\n[{n}/{total}] {text}", fg="cyan", bold=True))


def _ok(text: str) -> None:
    click.echo(click.style("  ✓ " + text, fg="green"))


def _err(text: str) -> None:
    click.echo(click.style("  ✗ " + text, fg="red"))


def _warn(text: str) -> None:
    click.echo(click.style("  ⚠ " + text, fg="yellow"))


def _info(text: str) -> None:
    click.echo("  " + text)


def _pid_is_alive(pid: int) -> bool:
    """Return True if the PID corresponds to a living process."""
    try:
        import psutil
        p = psutil.Process(pid)
        return p.is_running() and p.status() != "zombie"
    except Exception:
        return False


def _read_pid() -> int | None:
    try:
        return int(LOCK_FILE.read_text().strip())
    except Exception:
        return None


def _write_pid(pid: int) -> None:
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    LOCK_FILE.write_text(str(pid))


def _remove_pid() -> None:
    try:
        LOCK_FILE.unlink(missing_ok=True)
    except Exception:
        pass


# ---------------------------------------------------------------------------
# CLI group
# ---------------------------------------------------------------------------

@click.group()
@click.version_option(package_name="doit-fm")
def main() -> None:
    """doit-fm — AI agent that controls your computer via Telegram."""


# ---------------------------------------------------------------------------
# init
# ---------------------------------------------------------------------------

@main.command()
def init() -> None:
    """Interactive setup wizard. Run once before first use."""
    asyncio.run(_init_async())


async def _init_async() -> None:
    store = get_store()
    TOTAL = 4
    click.echo(click.style("\n🤖 doit-fm Setup Wizard", fg="magenta", bold=True))
    click.echo("=" * 50)

    # ------------------------------------------------------------------
    # Step 1: Dependencies
    # ------------------------------------------------------------------
    _print_step(1, TOTAL, "Checking dependencies")
    required = {
        "click": "click",
        "psutil": "psutil",
        "cryptography": "cryptography",
    }
    missing = []
    for name, pkg in required.items():
        try:
            __import__(name)
            _ok(f"{name}")
        except ImportError:
            _err(f"{name} — not installed")
            missing.append(pkg)

    if missing:
        click.echo(f"\n  Installing: {', '.join(missing)}")
        try:
            subprocess.run(
                [sys.executable, "-m", "pip", "install"] + missing,
                check=True,
                capture_output=False,
            )
            _ok("All dependencies installed")
        except subprocess.CalledProcessError as exc:
            _err(f"pip install failed: {exc}")
            sys.exit(1)

    # ------------------------------------------------------------------
    # Step 2: AI provider / model / API key
    # ------------------------------------------------------------------
    _print_step(2, TOTAL, "AI Provider Setup")
    click.echo("\n  Available providers:\n")

    providers = list(AI_PROVIDERS.items())
    for i, (key, info) in enumerate(providers, 1):
        free_count = sum(1 for m in info["models"] if m.get("free"))
        free_note = f"  ({free_count} free model{'s' if free_count != 1 else ''})" if free_count else ""
        click.echo(f"    {i:>2}. {info['display_name']}{free_note}")

    while True:
        choice = click.prompt("\n  Select provider number", type=int)
        if 1 <= choice <= len(providers):
            provider_key, provider_info = providers[choice - 1]
            break
        _err(f"Choose 1–{len(providers)}")

    click.echo(f"\n  Models for {provider_info['display_name']}:\n")
    for i, model in enumerate(provider_info["models"], 1):
        free = "  [FREE]" if model.get("free") else ""
        click.echo(f"    {i:>2}. {model['label']}{free}")

    while True:
        mchoice = click.prompt("\n  Select model number", type=int)
        if 1 <= mchoice <= len(provider_info["models"]):
            model = provider_info["models"][mchoice - 1]
            break
        _err(f"Choose 1–{len(provider_info['models'])}")

    click.echo(f"\n  Get your API key at: {click.style(provider_info['key_url'], fg='blue', underline=True)}")
    api_key = click.prompt("  Enter your API key", hide_input=True)

    # Validate
    click.echo("  Validating API key…")
    from .ai import validate_api_key
    ok, msg = validate_api_key(provider_key, api_key, model["id"])
    if ok:
        _ok(msg)
    else:
        _warn(f"{msg}")
        if not click.confirm("  Continue anyway?", default=False):
            sys.exit(1)

    store.set("ai_provider", provider_key)
    store.set("ai_model", model["id"])
    store.set("ai_api_key", api_key)
    store.set("ai_base_url", provider_info["base_url"])

    # ------------------------------------------------------------------
    # Step 3: Telegram bot
    # ------------------------------------------------------------------
    _print_step(3, TOTAL, "Telegram Bot Setup")
    click.echo("""
  How to get your Telegram Bot Token:
    1. Open Telegram and search for @BotFather
    2. Send: /newbot
    3. Follow the instructions and copy the token
    """)
    telegram_token = click.prompt("  Enter your Telegram Bot Token")

    click.echo("""
  How to find your Telegram User ID:
    1. Search for @userinfobot in Telegram
    2. Send /start — it will reply with your ID
    """)
    telegram_user_id = click.prompt("  Enter your Telegram User ID", type=int)

    # Test Telegram connection
    click.echo("  Testing Telegram connection…")
    import json as _json
    import urllib.request as _req
    test_url = f"https://api.telegram.org/bot{telegram_token}/sendMessage"
    payload = _json.dumps({
        "chat_id": telegram_user_id,
        "text": "✅ doit-fm is connected! Setup is complete.",
        "parse_mode": "Markdown",
    }).encode()
    req = _req.Request(test_url, data=payload,
                       headers={"Content-Type": "application/json"}, method="POST")
    try:
        with _req.urlopen(req, timeout=10) as resp:
            data = _json.loads(resp.read())
        if data.get("ok"):
            _ok("Message sent to your Telegram! Check your chat.")
        else:
            _warn(f"Telegram responded with error: {data.get('description', '?')}")
            if not click.confirm("  Continue anyway?", default=False):
                sys.exit(1)
    except Exception as exc:
        _warn(f"Could not reach Telegram: {exc}")
        if not click.confirm("  Continue anyway?", default=False):
            sys.exit(1)

    store.set("telegram_token", telegram_token)
    store.set("telegram_user_id", telegram_user_id)

    # ------------------------------------------------------------------
    # Step 4: Save
    # ------------------------------------------------------------------
    _print_step(4, TOTAL, "Saving configuration")
    LOG_DIR.mkdir(parents=True, exist_ok=True)
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    store.set("setup_complete", True)
    _ok(f"Config saved to {store.all() and 'encrypted store'}")

    click.echo(click.style("""
╔══════════════════════════════════════╗
║   ✅ Setup complete!                  ║
║                                      ║
║   Start your agent with:             ║
║     doit-fm run                      ║
║                                      ║
║   Or in the background:              ║
║     doit-fm run &                    ║
╚══════════════════════════════════════╝
""", fg="green"))


# ---------------------------------------------------------------------------
# run
# ---------------------------------------------------------------------------

@main.command()
@click.option("--foreground/--background", default=True,
              help="Run in foreground (default) or background.")
def run(foreground: bool) -> None:
    """Start the doit-fm agent."""
    store = get_store()
    if not store.exists():
        click.echo(click.style("❌ Not configured. Run: doit-fm init", fg="red"))
        sys.exit(1)

    # Check for existing instance
    existing_pid = _read_pid()
    if existing_pid and _pid_is_alive(existing_pid):
        click.echo(click.style(f"⚠️  Agent already running (PID {existing_pid}). Use 'doit-fm stop' first.", fg="yellow"))
        sys.exit(1)

    if not foreground:
        # Detach: re-exec with output redirected
        LOG_DIR.mkdir(parents=True, exist_ok=True)
        log_file = open(LOG_DIR / "agent.log", "a")
        proc = subprocess.Popen(
            [sys.executable, "-m", "doit_fm.cli", "run", "--foreground"],
            stdout=log_file,
            stderr=log_file,
            start_new_session=True,
        )
        _write_pid(proc.pid)
        click.echo(click.style(f"✅ Agent started in background (PID {proc.pid})", fg="green"))
        click.echo(f"  Logs: {LOG_DIR / 'agent.log'}")
        return

    asyncio.run(_run_async())


async def _run_async() -> None:
    store = get_store()
    config = store.all()

    # Write PID
    _write_pid(os.getpid())

    try:
        from .ai import AIEngine
        from .bot import TelegramBot
        from .tools import register_send_callback

        engine = AIEngine(config)
        bot = TelegramBot(
            token=config["telegram_token"],
            user_id=config["telegram_user_id"],
            engine=engine,
        )

        # Register reminder callback
        register_send_callback(bot.send_message)

        # Schedule startup message
        async def _startup_msg():
            await asyncio.sleep(STARTUP_DELAY)
            import platform as _pl
            await bot.send_message(
                f"🤖 *doit-fm agent started*\n"
                f"🖥️ {_pl.system()} {_pl.release()}\n"
                f"🧠 {config.get('ai_provider', '?')} / {config.get('ai_model', '?')}\n"
                f"💬 Send me a command or /help"
            )

        asyncio.ensure_future(_startup_msg())
        click.echo(click.style("🤖 doit-fm agent running. Press Ctrl+C to stop.", fg="green"))
        await bot.start()

    except KeyboardInterrupt:
        click.echo("\n⏹️  Stopped by user.")
    finally:
        _remove_pid()


# ---------------------------------------------------------------------------
# stop
# ---------------------------------------------------------------------------

@main.command()
def stop() -> None:
    """Stop the running doit-fm agent."""
    pid = _read_pid()
    if pid is None:
        click.echo("⚠️  No PID file found. Is the agent running?")
        return
    if not _pid_is_alive(pid):
        _remove_pid()
        click.echo("ℹ️  Agent was not running. Cleaned up stale PID file.")
        return
    try:
        os.kill(pid, signal.SIGTERM)
        # Wait up to 5 seconds
        for _ in range(50):
            if not _pid_is_alive(pid):
                break
            time.sleep(0.1)
        if _pid_is_alive(pid):
            os.kill(pid, signal.SIGKILL)
            click.echo(f"🔪 Force-killed PID {pid}")
        else:
            click.echo(click.style(f"✅ Agent (PID {pid}) stopped.", fg="green"))
        _remove_pid()
    except ProcessLookupError:
        _remove_pid()
        click.echo("ℹ️  Process already exited.")
    except PermissionError:
        click.echo(click.style("❌ Permission denied. Run as the same user that started the agent.", fg="red"))


# ---------------------------------------------------------------------------
# status
# ---------------------------------------------------------------------------

@main.command()
def status() -> None:
    """Show whether the doit-fm agent is running."""
    pid = _read_pid()
    store = get_store()

    click.echo("\n🤖 doit-fm Status")
    click.echo("─" * 30)

    if pid and _pid_is_alive(pid):
        click.echo(click.style(f"  Agent: ✅ Running (PID {pid})", fg="green"))
    elif pid:
        click.echo(click.style(f"  Agent: ⚠️  Stale PID {pid} (process not alive)", fg="yellow"))
    else:
        click.echo(click.style("  Agent: ❌ Not running", fg="red"))

    if store.exists():
        cfg = store.all()
        click.echo(click.style("  Config: ✅ Configured", fg="green"))
        click.echo(f"  Provider: {cfg.get('ai_provider', '?')} / {cfg.get('ai_model', '?')}")
        click.echo(f"  Telegram user ID: {cfg.get('telegram_user_id', '?')}")
    else:
        click.echo(click.style("  Config: ❌ Not configured (run: doit-fm init)", fg="red"))

    click.echo()


# ---------------------------------------------------------------------------
# doctor
# ---------------------------------------------------------------------------

@main.command()
def doctor() -> None:
    """Diagnose the doit-fm installation."""
    click.echo("\n🩺 doit-fm Doctor")
    click.echo("═" * 40)
    passed = 0
    failed = 0

    def check(label: str, ok: bool, detail: str = "") -> None:
        nonlocal passed, failed
        if ok:
            passed += 1
            _ok(f"{label}" + (f": {detail}" if detail else ""))
        else:
            failed += 1
            _err(f"{label}" + (f": {detail}" if detail else ""))

    # Python version
    v = sys.version_info
    check("Python version ≥ 3.10", v >= (3, 10), f"{v.major}.{v.minor}.{v.micro}")

    # Dependencies
    for mod, name in [("click", "click"), ("psutil", "psutil"), ("cryptography", "cryptography")]:
        try:
            __import__(mod)
            check(f"Package: {name}", True)
        except ImportError:
            check(f"Package: {name}", False, "not installed — run: pip install " + name)

    # Config
    store = get_store()
    check("Setup completed", store.exists(),
          "run 'doit-fm init'" if not store.exists() else "")

    if store.exists():
        cfg = store.all()
        check("Telegram token present", bool(cfg.get("telegram_token")))
        check("Telegram user_id present", bool(cfg.get("telegram_user_id")))
        check("AI provider set", bool(cfg.get("ai_provider")))
        check("AI model set", bool(cfg.get("ai_model")))
        check("AI API key present", bool(cfg.get("ai_api_key")))

    # Encryption key file
    from .config import ENCRYPTION_KEY_FILE
    check(
        "Encryption key file",
        ENCRYPTION_KEY_FILE.exists(),
        str(ENCRYPTION_KEY_FILE) if ENCRYPTION_KEY_FILE.exists() else "missing",
    )

    # Internet
    click.echo("\n  Testing internet connectivity…")
    for host, url in [("Google", "https://www.google.com"), ("Telegram API", "https://api.telegram.org")]:
        try:
            req = urllib.request.Request(url, method="HEAD")
            with urllib.request.urlopen(req, timeout=5):
                pass
            check(f"Reach {host}", True)
        except Exception as exc:
            check(f"Reach {host}", False, str(exc))

    # Agent running?
    pid = _read_pid()
    if pid and _pid_is_alive(pid):
        check("Agent running", True, f"PID {pid}")
    else:
        _warn("Agent: not running (start with: doit-fm run)")

    # Summary
    click.echo(f"\n{'─' * 40}")
    total = passed + failed
    color = "green" if failed == 0 else ("yellow" if failed <= 2 else "red")
    click.echo(click.style(f"  {passed}/{total} checks passed", fg=color, bold=True))
    if failed > 0:
        click.echo("  Run 'doit-fm init' to fix configuration issues.")
    click.echo()


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    main()
