"""
Main CLI entry point for AiCippy.

Provides all command-line commands using Typer with Rich UI.
Features auto-update checks and 60-minute session timeout login prompts.
"""

from __future__ import annotations

import asyncio
import json
import os
import re
import socket
import sys
import threading
from collections.abc import Callable
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any, Final, Optional

if TYPE_CHECKING:
    from aicippy.auth.models import AuthResult

import typer
from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich.align import Align
from rich.markdown import Markdown
from rich.prompt import Prompt

from aicippy import __version__
from aicippy.config import get_settings
from aicippy.utils.async_utils import run_sync
from aicippy.utils.logging import setup_logging, get_logger
from aicippy.utils.correlation import CorrelationContext
from aicippy.cli.ui_components import (
    BRAND_PRIMARY,
    BRAND_SECONDARY,
    BRAND_SUCCESS,
    BRAND_ERROR,
    BRAND_WARNING,
    BRAND_INFO,
)

# Session and version management
from aicippy.installer import (
    SessionManager,
    check_latest_version,
)

# Human verification
from aicippy.cli.human_verify import (
    HumanVerifier,
)

# Constants
UPDATE_CHECK_ENABLED: Final[bool] = True  # Auto-update on every app load
SESSION_CHECK_ENABLED: Final[bool] = True
HUMAN_VERIFICATION_ENABLED: Final[bool] = False  # Human verification disabled


def _detect_terminal_utf8() -> bool:
    """Detect whether the terminal supports UTF-8 encoding.

    Checks stdout encoding and the LANG/LC_ALL environment variables.
    Falls back to False if encoding cannot be determined.

    Returns:
        True if the terminal is UTF-8 capable, False otherwise.
    """
    # Check stdout encoding
    encoding = getattr(sys.stdout, "encoding", None) or ""
    if encoding.lower().replace("-", "") in ("utf8", "utf16", "utf32"):
        return True

    # Check environment variables (common on Linux/macOS)
    for env_var in ("LC_ALL", "LANG", "LC_CTYPE"):
        val = os.environ.get(env_var, "").lower()
        if "utf-8" in val or "utf8" in val:
            return True

    return False


def _is_ci_environment() -> bool:
    """Detect if running in a CI/CD environment.

    Checks common CI environment variables.

    Returns:
        True if running in CI, False otherwise.
    """
    ci_indicators = ("CI", "CONTINUOUS_INTEGRATION", "GITHUB_ACTIONS",
                     "GITLAB_CI", "JENKINS_URL", "CIRCLECI", "TRAVIS",
                     "BUILDKITE", "CODEBUILD_BUILD_ID")
    return any(os.environ.get(var) for var in ci_indicators)


# Terminal capability detection
_TERMINAL_UTF8: Final[bool] = _detect_terminal_utf8()
_IS_CI: Final[bool] = _is_ci_environment()

# Initialize Typer app with Rich
app = typer.Typer(
    name="aicippy",
    help="Enterprise-grade multi-agent CLI system powered by AWS Bedrock",
    add_completion=True,
    rich_markup_mode="rich",
    no_args_is_help=False,
)

# Rich console for output - force_terminal=True in CI for consistent rendering
console = Console(force_terminal=True) if _IS_CI else Console()
error_console = Console(stderr=True, force_terminal=True) if _IS_CI else Console(stderr=True)

# Logger
logger = get_logger(__name__)

# Email validation regex (RFC 5322 simplified)
_EMAIL_REGEX = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')

# Session manager instance (thread-safe singleton)
_session_manager: SessionManager | None = None
_session_manager_lock = threading.Lock()


def get_session_manager() -> SessionManager:
    """Get or create session manager singleton (thread-safe)."""
    global _session_manager
    if _session_manager is None:
        with _session_manager_lock:
            if _session_manager is None:
                _session_manager = SessionManager()
    return _session_manager


# Cache for validated login context (avoids double plan validation)
_login_plan_info: Any | None = None  # PlanInfo from login flow
_login_platform_client: Any | None = None  # PlatformClient from login flow


def get_login_context() -> tuple[Any | None, Any | None]:
    """Get cached plan_info and platform_client from login flow."""
    return _login_plan_info, _login_platform_client


async def check_for_updates_async(silent: bool = True) -> bool:
    """
    Check for updates and optionally perform silent upgrade.

    Args:
        silent: If True, run silently without UI output.

    Returns:
        True if update was performed, False otherwise.
    """
    if not UPDATE_CHECK_ENABLED:
        return False

    try:
        version_info = await check_latest_version()

        if version_info.update_available:
            if silent:
                # Silent auto-upgrade
                from aicippy.installer import perform_upgrade
                success, _ = await perform_upgrade()
                return success
            else:
                # Show update notification (only when explicitly requested)
                console.print(
                    Panel(
                        f"[yellow]Update available![/yellow]\n"
                        f"Current: {version_info.current}\n"
                        f"Latest: {version_info.latest}\n\n"
                        f"Run [bold cyan]aicippy upgrade[/bold cyan] to update.",
                        title="Update Available",
                        border_style="yellow",
                    )
                )
        return False
    except Exception as update_err:
        logger.debug("update_check_failed", error=str(update_err))
        return False


def check_for_updates(silent: bool = True) -> None:
    """Synchronous wrapper for update check (silent by default)."""
    try:
        asyncio.run(check_for_updates_async(silent=silent))
    except Exception as sync_update_err:
        logger.debug("sync_update_check_failed", error=str(sync_update_err))


def _perform_auto_update() -> None:
    """Check for updates and auto-upgrade if available. No user permission needed.

    This runs synchronously at app startup, blocking until the check completes.
    If an update is available, it is installed and the process re-execs itself
    so the new code runs immediately.

    Failures are silently swallowed to never block app startup.
    """
    async def _auto_update_async() -> bool:
        from aicippy.installer.main import check_latest_version as _check_version
        from aicippy.installer.main import perform_upgrade as _perform_upgrade

        version_info = await _check_version()
        if version_info.update_available and version_info.latest:
            console.print(
                f"  [dim]Updating AiCippy {version_info.current} "
                f"\u2192 {version_info.latest}...[/dim]"
            )
            success, _msg = await _perform_upgrade(silent=True)
            if success:
                console.print(
                    f"  [green]\u2713[/green] Updated to {version_info.latest}"
                )
                return True
        return False

    # Guard against infinite re-exec: skip if we already updated this invocation
    if os.environ.get("_AICIPPY_UPDATED") == "1":
        return

    try:
        updated = asyncio.run(_auto_update_async())
        if updated:
            # Re-exec the process so the newly installed code runs immediately.
            # This replaces the current process with the updated binary.
            import shutil
            aicippy_path = shutil.which("aicippy")
            if aicippy_path:
                os.environ["_AICIPPY_UPDATED"] = "1"
                os.execv(aicippy_path, sys.argv)
            # If execv fails or aicippy not found, continue with old code
    except Exception as auto_update_err:
        # Never block app startup due to update failure
        logger.debug("auto_update_failed", error=str(auto_update_err))


def _ensure_dependencies() -> None:
    """Check and install missing or outdated dependencies with animated progress.

    Runs silently in the background; shows a single progress bar in the
    foreground. Skips entirely if all dependencies are satisfied.
    Failures are swallowed to never block app startup.
    """
    try:
        from rich.progress import (
            BarColumn,
            Progress,
            SpinnerColumn,
            TextColumn,
        )
        from aicippy.installer.dependency_manager import check_dependencies

        dep_check = check_dependencies()
        if dep_check.all_satisfied:
            return  # Nothing to do

        packages_needed = [
            pkg for pkg in dep_check.packages
            if not pkg.is_installed or pkg.needs_upgrade
        ]
        if not packages_needed:
            return

        total_steps = len(packages_needed) + 1  # +1 for pip upgrade

        with Progress(
            SpinnerColumn(style=BRAND_PRIMARY),
            TextColumn(f"[{BRAND_PRIMARY}]{{task.description}}[/{BRAND_PRIMARY}]"),
            BarColumn(bar_width=40, complete_style=BRAND_PRIMARY, finished_style=BRAND_SUCCESS),
            TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
            console=console,
            transient=True,
        ) as progress:
            task = progress.add_task("Preparing environment...", total=total_steps)

            import subprocess

            # Upgrade pip first
            base_flags = ["--quiet", "--disable-pip-version-check", "--no-warn-script-location"]
            try:
                subprocess.run(
                    [sys.executable, "-m", "pip", "install", "--upgrade", "pip", *base_flags],
                    capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL,
                )
            except Exception as pip_err:
                logger.debug("pip_self_upgrade_failed", error=str(pip_err))
            progress.update(task, advance=1, description="Installing dependencies...")

            # Install all needed packages in one pip call
            package_specs = [f"{pkg.name}>={pkg.required_version}" for pkg in packages_needed]
            try:
                subprocess.run(
                    [sys.executable, "-m", "pip", "install", "--upgrade", *base_flags, *package_specs],
                    capture_output=True, text=True, timeout=600, stdin=subprocess.DEVNULL,
                )
            except Exception as dep_err:
                logger.debug("dependency_install_failed", error=str(dep_err))

            # Mark all package steps complete
            progress.update(task, completed=total_steps, description="Environment ready")

        console.print(f"  [{BRAND_SUCCESS}]\u2713[/{BRAND_SUCCESS}] Dependencies verified")

    except Exception as ensure_err:
        # Never block startup, but log the failure
        logger.debug("ensure_dependencies_failed", error=str(ensure_err))


def check_session_and_prompt_login() -> bool:
    """
    Check if session is valid, prompt for login if expired.

    Behavior:
    - Valid session: silently continue (no output).
    - Recently expired session (< 30 min): show brief message, attempt
      token refresh before falling back to full interactive login.
    - Invalid/missing session: proceed to full interactive login flow.

    Returns:
        True if session is valid or user successfully logged in.
    """
    if not SESSION_CHECK_ENABLED:
        return True

    try:
        from datetime import datetime, timedelta, timezone
        from aicippy.installer.session_manager import SESSION_EXPIRED

        session_mgr = get_session_manager()
        validation = session_mgr.validate()

        if not validation.needs_login:
            # Session is valid - silently refresh activity
            session_mgr.touch()
            return True

        # --- Session requires login ---
        # Check if the session expired recently (within 30 minutes)
        if (
            validation.status == SESSION_EXPIRED
            and validation.session is not None
        ):
            now = datetime.now(timezone.utc)
            time_since_expiry = now - validation.session.expires_at
            recently_expired = time_since_expiry < timedelta(minutes=30)

            if recently_expired:
                # Attempt silent token refresh first
                try:
                    from aicippy.auth.cognito import CognitoAuth

                    auth = CognitoAuth()
                    tokens = auth.get_current_tokens()

                    if tokens is not None and not tokens.is_expired():
                        # Tokens are still valid -- refresh the session
                        with console.status(
                            f"[{BRAND_PRIMARY}]  Session expired. Re-authenticating...[/{BRAND_PRIMARY}]",
                            spinner="dots",
                            spinner_style=BRAND_PRIMARY,
                        ):
                            success, _ = session_mgr.refresh()

                        if success:
                            session_mgr.touch()
                            return True
                except Exception as refresh_err:
                    # Token refresh failed; fall through to full login
                    logger.debug("token_refresh_failed_during_session_check", error=str(refresh_err))

                # Token refresh did not succeed -- show brief notice then login
                console.print(
                    f"\n[{BRAND_WARNING}]  Session expired. Please log in again.[/{BRAND_WARNING}]\n"
                )
                return perform_interactive_login()

        # Session invalid or missing - trigger full login directly
        return perform_interactive_login()

    except Exception as e:
        logger.warning("session_check_failed", error=str(e))
        return False  # Block access on session check failure


def _show_login_progress(
    console: Console,
    steps: list[tuple[str, str]],
) -> None:
    """
    Show animated step-by-step login progress using Rich Live display.

    Each step transitions from pending to running to complete:
      pending  ->  running  ->  complete
        (dim)    (spinner)    (green check)

    Falls back to ASCII symbols when the terminal does not support UTF-8.

    Args:
        console: Rich console instance.
        steps: List of (step_key, step_label) tuples. Each step is shown
               sequentially with animation.
    """
    import time as _time

    # Select symbols based on terminal UTF-8 support
    sym_pending = "  o " if not _TERMINAL_UTF8 else "  \u25cb "
    sym_running = "  > " if not _TERMINAL_UTF8 else "  \u25d0 "
    sym_complete = "  * " if not _TERMINAL_UTF8 else "  \u2713 "

    step_states: list[str] = ["pending"] * len(steps)

    def _render_steps() -> Text:
        """Render all steps with their current visual state."""
        output = Text()
        for idx, (_, label) in enumerate(steps):
            state = step_states[idx]
            if state == "pending":
                output.append(sym_pending, style="dim")
                output.append(label, style="dim")
            elif state == "running":
                output.append(sym_running, style=f"bold {BRAND_PRIMARY}")
                output.append(label, style=BRAND_PRIMARY)
                output.append(" ...", style="dim")
            elif state == "complete":
                output.append(sym_complete, style=f"bold {BRAND_SUCCESS}")
                output.append(label, style=BRAND_SUCCESS)
            if idx < len(steps) - 1:
                output.append("\n")
        return output

    with Live(
        _render_steps(),
        console=console,
        refresh_per_second=8,
        transient=True,
    ) as live:
        for i in range(len(steps)):
            # Mark current step as running
            step_states[i] = "running"
            live.update(_render_steps())
            _time.sleep(0.4)

            # Mark current step as complete
            step_states[i] = "complete"
            live.update(_render_steps())
            _time.sleep(0.15)

    # Print final state (non-transient) so it persists
    console.print(_render_steps())


def _complete_login_after_auth(
    result: "AuthResult",
    target_console: Console,
) -> bool:
    """Complete the login flow after successful authentication.

    Shared post-auth logic for all login methods (email+password, OTP,
    browser, social). Performs plan validation, session creation, and
    displays the success summary.

    Args:
        result: Successful AuthResult with tokens.
        target_console: Rich console for output.

    Returns:
        True if session was created successfully.

    Raises:
        typer.Exit: On plan validation failure or session creation failure.
    """
    from aicippy.cli.mascot import animate_welcome_compact

    session_mgr = get_session_manager()
    user_email = result.user_email or ""
    username = user_email.split("@")[0] if user_email else "user"

    # === PLAN VALIDATION GATE ===
    plan_info = None
    plan_metadata: dict[str, object] = {}

    with target_console.status(
        f"[{BRAND_PRIMARY}]  Validating plan...[/{BRAND_PRIMARY}]",
        spinner="dots",
        spinner_style=BRAND_PRIMARY,
    ):
        try:
            from aicippy.billing.plan_validator import PlanValidator

            settings = get_settings()
            platform_client = None
            try:
                from aicippy.platform.client import PlatformClient

                if result.tokens and result.tokens.access_token:
                    platform_client = PlatformClient(
                        base_url=settings.platform_api_url,
                        auth_token=result.tokens.access_token,
                    )
            except Exception as platform_err:
                logger.debug("platform_client_init_failed_fallback_to_dynamodb", error=str(platform_err))

            validator = PlanValidator(platform_client=platform_client)
            plan_info = validator.validate(user_email)

            if not plan_info.is_valid_for_aicippy:
                reason = plan_info.denial_reason or "Access denied"
                plan_url = "https://vibekaro.ai/plans"
                target_console.print(Panel(
                    f"[{BRAND_ERROR} bold]Access Denied[/{BRAND_ERROR} bold]\n\n"
                    f"{reason}\n\n"
                    f"[{BRAND_PRIMARY}]Subscribe to CHAKRA plan:[/{BRAND_PRIMARY}] {plan_url}\n"
                    f"[dim]Questions? Contact support@aivibe.in[/dim]",
                    title=f"[{BRAND_ERROR}]Subscription Required[/{BRAND_ERROR}]",
                    border_style=BRAND_ERROR,
                ))
                session_mgr.logout()
                raise typer.Exit(1)

            plan_metadata = {
                "plan": plan_info.plan,
                "credits": plan_info.credits_remaining,
                "is_admin": plan_info.is_admin,
                "tenant_id": plan_info.tenant_id,
                "platform_api_url": settings.platform_api_url if platform_client else "",
                "app_api_url": settings.app_api_url if platform_client else "",
            }

            # Cache for interactive session to avoid re-validation
            global _login_plan_info, _login_platform_client
            _login_plan_info = plan_info
            _login_platform_client = platform_client

        except typer.Exit:
            raise
        except Exception as plan_err:
            logger.warning("plan_validation_failed_at_login", error=str(plan_err))
            target_console.print(Panel(
                f"[{BRAND_ERROR} bold]Plan Validation Failed[/{BRAND_ERROR} bold]\n\n"
                f"Unable to verify your subscription.\n\n"
                f"[dim]Please try again later or contact support@aivibe.in[/dim]\n"
                f"[dim]Plans: https://vibekaro.ai/plans[/dim]",
                title=f"[{BRAND_ERROR}]Service Unavailable[/{BRAND_ERROR}]",
                border_style=BRAND_ERROR,
            ))
            raise typer.Exit(1)

    # --- Setting up workspace with spinner ---
    with target_console.status(
        f"[{BRAND_PRIMARY}]  Setting up workspace...[/{BRAND_PRIMARY}]",
        spinner="dots",
        spinner_style=BRAND_PRIMARY,
    ):
        login_success, _ = session_mgr.login(
            user_id=result.user_id or "unknown",
            username=username,
            email=user_email,
            metadata=plan_metadata,
        )

    if login_success:
        try:
            _show_login_progress(target_console, [
                ("auth", "Authenticated"),
                ("plan", "Plan validated"),
                ("workspace", "Workspace ready"),
            ])
        except Exception as anim_err:
            logger.debug("login_progress_animation_failed", error=str(anim_err))

        target_console.print()

        _check = "  * " if not _TERMINAL_UTF8 else "  \u2713 "
        summary = Text()
        summary.append(_check, style=f"bold {BRAND_SUCCESS}")
        summary.append("Authenticated as ", style=BRAND_SUCCESS)
        summary.append(user_email, style=f"bold {BRAND_SUCCESS}")
        summary.append("\n")

        if plan_info is not None:
            summary.append(_check, style=f"bold {BRAND_SUCCESS}")
            if plan_info.is_admin:
                summary.append("Plan: ADMIN (unlimited credits)", style=BRAND_SUCCESS)
            else:
                summary.append("Plan: ", style=BRAND_SUCCESS)
                summary.append(
                    f"{plan_info.plan.upper()} "
                    f"({plan_info.credits_remaining}/{plan_info.credits_total} credits)",
                    style=BRAND_SUCCESS,
                )
            summary.append("\n")

        summary.append(_check, style=f"bold {BRAND_SUCCESS}")
        summary.append("Workspace ready", style=BRAND_SUCCESS)

        target_console.print(summary)
        target_console.print()

        if _TERMINAL_UTF8:
            run_sync(animate_welcome_compact(target_console, username))
        else:
            target_console.print(
                Panel(
                    f"Welcome, {username}! Let's build something amazing!",
                    title="AiCippy",
                    border_style=BRAND_PRIMARY,
                )
            )

        return True

    target_console.print(
        f"[{BRAND_ERROR}]  Failed to create session. "
        f"Please try again.[/{BRAND_ERROR}]"
    )
    raise typer.Exit(1)


def _perform_otp_login_flow() -> bool:
    """Run the Email OTP login flow.

    Prompts for email, sends OTP via Cognito CUSTOM_AUTH, then prompts
    for the 6-digit code and verifies it. On success, completes the
    full login flow (plan validation + session setup).

    Returns:
        True if login successful, False if OTP initiation failed
        (caller should offer alternative login methods).

    Raises:
        typer.Exit: Only on explicit user cancellation.
    """
    from aicippy.auth.cognito import CognitoAuth

    # Prompt for email
    email = Prompt.ask(f"[{BRAND_PRIMARY}]  Email[/{BRAND_PRIMARY}]")
    if not email or not email.strip():
        console.print(f"[{BRAND_ERROR}]  Cancelled.[/{BRAND_ERROR}]")
        raise typer.Exit(1)
    email = email.strip()

    if not _EMAIL_REGEX.match(email):
        console.print(
            f"[{BRAND_ERROR}]  Invalid email format.[/{BRAND_ERROR}]"
        )
        return False

    auth = CognitoAuth()

    # Initiate OTP
    otp_result = None
    with console.status(
        f"[{BRAND_PRIMARY}]  Sending OTP to {email}...[/{BRAND_PRIMARY}]",
        spinner="dots",
        spinner_style=BRAND_PRIMARY,
    ):
        try:
            otp_result = run_sync(auth.otp_login(email))
        except Exception as e:
            console.print(f"[{BRAND_ERROR}]  OTP request failed: {e}[/{BRAND_ERROR}]")
            return False

    if otp_result is None:
        console.print(f"[{BRAND_ERROR}]  OTP request failed unexpectedly.[/{BRAND_ERROR}]")
        return False

    # If we got tokens directly (unlikely), proceed
    if otp_result.success:
        return _complete_login_after_auth(otp_result, console)

    # otp_login returns success=False with session in error field on CUSTOM_CHALLENGE
    session_token = otp_result.error
    if not session_token or otp_result.user_email is None:
        error_msg = session_token or "OTP initiation failed."
        console.print(
            f"[{BRAND_ERROR}]  {error_msg}[/{BRAND_ERROR}]"
        )
        console.print(
            f"[{BRAND_WARNING}]  Try a different login method.[/{BRAND_WARNING}]"
        )
        return False

    console.print(
        f"[{BRAND_SUCCESS}]  OTP sent to {email}. Check your inbox.[/{BRAND_SUCCESS}]"
    )

    # Prompt for OTP code
    otp_code = Prompt.ask(
        f"[{BRAND_PRIMARY}]  Enter 6-digit OTP code[/{BRAND_PRIMARY}]"
    )
    if not otp_code or not otp_code.strip():
        console.print(f"[{BRAND_ERROR}]  Cancelled.[/{BRAND_ERROR}]")
        raise typer.Exit(1)

    otp_code = otp_code.strip()

    # Verify OTP
    verify_result = None
    with console.status(
        f"[{BRAND_PRIMARY}]  Verifying OTP...[/{BRAND_PRIMARY}]",
        spinner="dots",
        spinner_style=BRAND_PRIMARY,
    ):
        try:
            verify_result = run_sync(auth.verify_otp(email, otp_code, session_token))
        except Exception as e:
            console.print(f"[{BRAND_ERROR}]  OTP verification failed: {e}[/{BRAND_ERROR}]")
            return False

    if verify_result is None:
        console.print(f"[{BRAND_ERROR}]  OTP verification failed unexpectedly.[/{BRAND_ERROR}]")
        return False

    if not verify_result.success:
        error_msg = verify_result.error or "OTP verification failed"
        console.print(f"[{BRAND_ERROR}]  {error_msg}[/{BRAND_ERROR}]")
        return False

    return _complete_login_after_auth(verify_result, console)


def perform_interactive_login() -> bool:
    """
    Perform interactive login via direct Cognito auth.

    Presents a login method selection menu:
      1. Email + Password (direct Cognito USER_PASSWORD_AUTH)
      2. Email OTP (CUSTOM_AUTH flow - sends verification code to email)
      3. Google Login (opens browser via Cognito hosted UI)
      4. GitHub Login (opens browser via Cognito hosted UI)

    Features a polished UX with:
    - Login method selection menu
    - Branded header panel
    - Email validation before network call
    - Spinner-based progress during authentication
    - Friendly, actionable error messages
    - Compact success status summary

    Returns:
        True if login successful, exits to terminal on failure.
    """
    _login_attempt_count = 0
    _max_login_attempts = 3

    try:
        from aicippy.auth.cognito import CognitoAuth

        # --- Branded header panel ---
        header_text = Text()
        header_text.append("AiCippy", style=f"bold {BRAND_PRIMARY}")
        header_text.append(f"  v{__version__}", style="dim")
        header_text.append("\n")
        header_text.append("Enterprise Multi-Agent CLI", style=f"italic {BRAND_SECONDARY}")

        console.print()
        console.print(Panel(
            Align.center(header_text),
            border_style=BRAND_PRIMARY,
            padding=(1, 4),
        ))
        console.print()

        # --- Login method selection (with retry on failure) ---
        while True:
            login_menu = Table(
                show_header=False,
                show_edge=True,
                box=None,
                padding=(0, 2),
            )
            login_menu.add_column("option", style=f"bold {BRAND_PRIMARY}")
            login_menu.add_column("description")

            login_menu.add_row("1.", "Email + Password")
            login_menu.add_row("2.", "Email OTP (verification code)")
            login_menu.add_row("3.", "Google Login (opens browser)")
            login_menu.add_row("4.", "GitHub Login (opens browser)")

            console.print(Panel(
                login_menu,
                title=f"[bold {BRAND_PRIMARY}]Choose login method[/bold {BRAND_PRIMARY}]",
                border_style=BRAND_PRIMARY,
                padding=(1, 2),
            ))

            login_choice = Prompt.ask(
                f"[{BRAND_PRIMARY}]  Enter choice[/{BRAND_PRIMARY}]",
                choices=["1", "2", "3", "4"],
                default="1",
            )

            # --- Route to selected login method ---
            if login_choice in ("3", "4"):
                # Social login via browser-based OAuth (reuses PKCE flow)
                if login_choice == "3":
                    provider_name = "Google"
                    identity_provider = "Google"
                else:
                    provider_name = "GitHub"
                    identity_provider = "SignInWithGitHub"
                console.print(
                    f"[{BRAND_PRIMARY}]  Opening browser for {provider_name} login...[/{BRAND_PRIMARY}]"
                )
                browser_result = perform_browser_login(identity_provider=identity_provider)
                if browser_result:
                    return True
                # Browser login failed - show menu again
                console.print()
                continue

            if login_choice == "2":
                otp_result = _perform_otp_login_flow()
                if otp_result:
                    return True
                # OTP failed (e.g. CUSTOM_AUTH not enabled) - show menu again
                console.print()
                continue

            # Option 1 selected - break out to email+password flow below
            break

        # --- Option 1: Email + Password flow ---
        while _login_attempt_count < _max_login_attempts:
            _login_attempt_count += 1

            # --- Email prompt with validation ---
            email = Prompt.ask(
                f"[{BRAND_PRIMARY}]  Email[/{BRAND_PRIMARY}]",
            )
            if not email or not email.strip():
                console.print(f"[{BRAND_ERROR}]  Cancelled.[/{BRAND_ERROR}]")
                raise typer.Exit(1)

            email = email.strip()

            # Email format validation before hitting the network
            if not _EMAIL_REGEX.match(email):
                console.print(
                    f"[{BRAND_ERROR}]  Invalid email format. "
                    f"Please enter a valid email address.[/{BRAND_ERROR}]"
                )
                if _login_attempt_count < _max_login_attempts:
                    console.print()
                    continue
                raise typer.Exit(1)

            # --- Password prompt (with getpass fallback for terminals
            # that do not support Rich Prompt password masking) ---
            password: str | None = None
            try:
                password = Prompt.ask(
                    f"[{BRAND_PRIMARY}]  Password[/{BRAND_PRIMARY}]",
                    password=True,
                )
            except Exception as prompt_err:
                # Fallback: use stdlib getpass which works on all terminals
                logger.debug("rich_password_prompt_failed_using_getpass", error=str(prompt_err))
                import getpass as _getpass

                try:
                    password = _getpass.getpass("  Password: ")
                except (EOFError, KeyboardInterrupt):
                    password = None

            if not password:
                console.print(f"[{BRAND_ERROR}]  Cancelled.[/{BRAND_ERROR}]")
                raise typer.Exit(1)

            console.print()

            # --- Authenticate with spinner ---
            auth = CognitoAuth()
            result = None
            auth_error_friendly = None

            with console.status(
                f"[{BRAND_PRIMARY}]  Authenticating...[/{BRAND_PRIMARY}]",
                spinner="dots",
                spinner_style=BRAND_PRIMARY,
            ):
                try:
                    result = run_sync(auth.login(email, password))
                except Exception as net_err:
                    error_str = str(net_err).lower()
                    if "timeout" in error_str or "timed out" in error_str:
                        auth_error_friendly = (
                            "Connection timed out. Check your internet and try again."
                        )
                    elif "connection" in error_str or "network" in error_str:
                        auth_error_friendly = (
                            "Network error. Check your internet connection and try again."
                        )
                    else:
                        auth_error_friendly = f"Authentication error: {net_err}"

            # Handle network-level errors
            if auth_error_friendly is not None:
                console.print(f"[{BRAND_ERROR}]  {auth_error_friendly}[/{BRAND_ERROR}]")
                if _login_attempt_count < _max_login_attempts:
                    console.print()
                    continue
                raise typer.Exit(1)

            if result is None:
                console.print(f"[{BRAND_ERROR}]  Authentication failed unexpectedly.[/{BRAND_ERROR}]")
                raise typer.Exit(1)

            # --- Handle login failure with friendly messages ---
            if not result.success:
                error_msg = result.error or "Login failed"
                error_lower = error_msg.lower()

                if "invalid email or password" in error_lower:
                    remaining = _max_login_attempts - _login_attempt_count
                    console.print(
                        f"[{BRAND_ERROR}]  Incorrect email or password.[/{BRAND_ERROR}]"
                    )
                    if remaining > 0:
                        console.print(
                            f"[dim]  {remaining} attempt(s) remaining. "
                            f"Forgot password? Visit https://auth.aivibe.cloud[/dim]"
                        )
                        console.print()
                        continue
                    else:
                        console.print(
                            f"[{BRAND_WARNING}]  Too many failed attempts. "
                            f"Reset password at https://auth.aivibe.cloud[/{BRAND_WARNING}]"
                        )
                        raise typer.Exit(1)
                elif "not verified" in error_lower or "not confirmed" in error_lower:
                    console.print(
                        f"[{BRAND_WARNING}]  Account not verified. "
                        f"Check your email for a verification link.[/{BRAND_WARNING}]"
                    )
                    raise typer.Exit(1)
                elif "password reset" in error_lower:
                    console.print(
                        f"[{BRAND_WARNING}]  Password reset required. "
                        f"Visit https://auth.aivibe.cloud to set a new password.[/{BRAND_WARNING}]"
                    )
                    raise typer.Exit(1)
                elif "locked" in error_lower or "disabled" in error_lower:
                    console.print(
                        f"[{BRAND_ERROR}]  Account locked. "
                        f"Contact support@aivibe.in for assistance.[/{BRAND_ERROR}]"
                    )
                    raise typer.Exit(1)
                else:
                    console.print(f"[{BRAND_ERROR}]  {error_msg}[/{BRAND_ERROR}]")
                    if _login_attempt_count < _max_login_attempts:
                        console.print()
                        continue
                    raise typer.Exit(1)

            # === LOGIN SUCCESSFUL - Complete via shared post-auth flow ===
            return _complete_login_after_auth(result, console)

        # Exhausted all attempts
        console.print(
            f"[{BRAND_ERROR}]  Maximum login attempts reached. "
            f"Please try again later.[/{BRAND_ERROR}]"
        )
        raise typer.Exit(1)

    except ImportError as e:
        logger.error("auth_module_not_available", error=str(e))
        console.print(
            f"[{BRAND_ERROR}]  Authentication module not available: {e}[/{BRAND_ERROR}]"
        )
        raise typer.Exit(1)

    except typer.Exit:
        raise
    except Exception as e:
        logger.exception("login_failed", error=str(e))
        raise typer.Exit(1)


def perform_browser_login(identity_provider: str | None = None) -> bool:
    """
    Perform browser-based OAuth PKCE login via Cognito hosted UI.

    Opens the default browser to auth.aivibe.cloud for authentication.
    A lightweight localhost HTTP server catches the callback.
    Displays a manual URL fallback if the browser fails to open.

    Features:
    - PKCE S256 flow (no client secret required)
    - Automatic port fallback if 9876 is busy
    - 120-second timeout with clear messaging
    - Same plan validation and session setup as password login
    - Optional identity_provider for direct Google/GitHub redirect

    Args:
        identity_provider: Optional Cognito identity provider name
            (e.g. "Google", "SignInWithGitHub") to skip the hosted UI.

    Returns:
        True if login successful, exits to terminal on failure.
    """
    try:
        from aicippy.auth.browser_auth import BrowserAuth

        # --- Branded header panel ---
        header_text = Text()
        header_text.append("AiCippy", style=f"bold {BRAND_PRIMARY}")
        header_text.append(f"  v{__version__}", style="dim")
        header_text.append("\n")
        header_text.append("Browser Authentication", style=f"italic {BRAND_SECONDARY}")

        console.print()
        console.print(Panel(
            Align.center(header_text),
            border_style=BRAND_PRIMARY,
            padding=(1, 4),
        ))
        console.print()

        auth = BrowserAuth()
        result = None
        auth_error_friendly = None

        # Run the browser login flow
        # The login() method opens the browser and starts the callback server
        console.print(f"[{BRAND_PRIMARY}]  Opening browser for authentication...[/{BRAND_PRIMARY}]")

        try:
            result = run_sync(auth.login(identity_provider=identity_provider))
        except Exception as net_err:
            error_str = str(net_err).lower()
            if "timeout" in error_str or "timed out" in error_str:
                auth_error_friendly = (
                    "Authentication timed out. Please try again."
                )
            elif "connection" in error_str or "network" in error_str:
                auth_error_friendly = (
                    "Network error. Check your internet connection and try again."
                )
            elif "port" in error_str:
                auth_error_friendly = (
                    "Could not start callback server. Try closing other applications "
                    "using ports 9876-9878."
                )
            else:
                auth_error_friendly = f"Browser authentication error: {net_err}"

        # Show manual URL fallback after browser attempt
        if auth is not None and auth.last_auth_url:
            if not auth.browser_opened:
                console.print(
                    f"\n[{BRAND_WARNING}]  Browser did not open automatically.[/{BRAND_WARNING}]"
                )
            console.print(f"[dim]  If browser didn't open, visit:[/dim]")
            console.print(f"  [{BRAND_INFO}]{auth.last_auth_url}[/{BRAND_INFO}]")
            console.print()

        # Handle errors - return False so caller can offer alternative login methods
        if auth_error_friendly is not None:
            console.print(f"[{BRAND_ERROR}]  {auth_error_friendly}[/{BRAND_ERROR}]")
            console.print(f"[{BRAND_WARNING}]  Try a different login method.[/{BRAND_WARNING}]")
            return False

        if result is None:
            console.print(f"[{BRAND_ERROR}]  Authentication failed unexpectedly.[/{BRAND_ERROR}]")
            console.print(f"[{BRAND_WARNING}]  Try a different login method.[/{BRAND_WARNING}]")
            return False

        if not result.success:
            error_msg = result.error or "Login failed"
            console.print(f"[{BRAND_ERROR}]  {error_msg}[/{BRAND_ERROR}]")
            console.print(f"[{BRAND_WARNING}]  Try a different login method.[/{BRAND_WARNING}]")
            return False

        # === LOGIN SUCCESSFUL - Complete via shared post-auth flow ===
        return _complete_login_after_auth(result, console)

    except ImportError as e:
        logger.error("browser_auth_module_not_available", error=str(e))
        console.print(
            f"[{BRAND_ERROR}]  Browser authentication module not available: {e}[/{BRAND_ERROR}]"
        )
        return False

    except typer.Exit:
        raise
    except Exception as e:
        logger.exception("browser_login_failed", error=str(e))
        console.print(f"[{BRAND_ERROR}]  Browser login failed: {e}[/{BRAND_ERROR}]")
        console.print(f"[{BRAND_WARNING}]  Try a different login method.[/{BRAND_WARNING}]")
        return False


def display_welcome_banner() -> None:
    """Display the welcome banner (silent - no output)."""
    # Welcome animation is shown during login success
    # This function is kept for compatibility but produces no output
    pass


# Human verification state (disabled - kept for interface compatibility)
_human_verifier: HumanVerifier | None = None
_human_verified: bool = False  # Always set True by perform_human_verification_after_login


def get_human_verifier() -> HumanVerifier:
    """Get or create human verifier singleton."""
    global _human_verifier
    if _human_verifier is None:
        _human_verifier = HumanVerifier(console=console)
    return _human_verifier


def check_automation_and_verify() -> bool:
    """
    Check for automation and perform human verification.

    Returns:
        True always - automation checks are disabled.
    """
    return True


def perform_human_verification_after_login() -> bool:
    """
    Perform human verification with puzzle question after login.

    Returns:
        True always - human verification is disabled.
    """
    global _human_verified
    _human_verified = True
    return True


def version_callback(value: bool) -> None:
    """Display version information and exit."""
    if value:
        console.print(
            Panel(
                f"[bold blue]AiCippy[/bold blue] v{__version__}\n"
                f"Enterprise Multi-Agent CLI System\n\n"
                f"[dim]Copyright (c) 2024-2026 AiVibe Software Services Pvt Ltd[/dim]\n"
                f"[dim]ISO 27001:2022 | NVIDIA Inception | AWS Activate[/dim]",
                title="Version",
                border_style="blue",
            )
        )
        raise typer.Exit()


@app.callback(invoke_without_command=True)
def main(
    ctx: typer.Context,
    version: Annotated[
        Optional[bool],
        typer.Option(
            "--version",
            "-v",
            callback=version_callback,
            is_eager=True,
            help="Show version and exit",
        ),
    ] = None,
    verbose: Annotated[
        bool,
        typer.Option(
            "--verbose",
            "-V",
            help="Enable verbose logging",
        ),
    ] = False,
    config_dir: Annotated[
        Optional[Path],
        typer.Option(
            "--config-dir",
            "-c",
            help="Custom configuration directory",
        ),
    ] = None,
    skip_update_check: Annotated[
        bool,
        typer.Option(
            "--skip-update-check",
            help="Skip automatic update check",
            hidden=True,
        ),
    ] = False,
    skip_login: Annotated[
        bool,
        typer.Option(
            "--skip-login",
            help="Skip login check (offline mode)",
            hidden=True,
        ),
    ] = False,
) -> None:
    """
    AiCippy - Enterprise-grade multi-agent CLI system.

    Run without arguments to start an interactive session.
    Use --help to see all available commands.

    Features:
    - Auto version update checks on launch
    - 60-minute session timeout with automatic re-login prompt
    - Enterprise-grade security with AWS Cognito authentication
    """
    # Initialize settings
    settings = get_settings()

    # Setup logging based on verbose flag
    if verbose:
        os.environ["AICIPPY_LOG_LEVEL"] = "DEBUG"
        # Clear cached settings to reload with new log level
        get_settings.cache_clear()
        settings = get_settings()

    setup_logging(settings)

    # FIRST: Check for automation/robot access
    if not check_automation_and_verify():
        logger.warning("automation_blocked", reason="automated_access_detected")
        raise typer.Exit(1)

    # Mandatory auto-update: check and install before any other operation
    if not skip_update_check:
        _perform_auto_update()

    # Ensure all dependencies are present (silent unless install needed)
    _ensure_dependencies()

    # Session validation - silent until login needed
    if not skip_login:
        session_valid = check_session_and_prompt_login()
        if not session_valid:
            logger.warning("session_invalid_access_denied")
            raise typer.Exit(1)

        # Perform human verification after successful login
        if not perform_human_verification_after_login():
            logger.warning("human_verification_failed")
            raise typer.Exit(1)

    # If no subcommand is provided, start interactive session
    if ctx.invoked_subcommand is None:
        # Display welcome banner with session info
        display_welcome_banner()

        from aicippy.cli.interactive import start_interactive_session
        asyncio.run(start_interactive_session())


@app.command()
def login(
    force: Annotated[
        bool,
        typer.Option(
            "--force",
            "-f",
            help="Force re-authentication even if already logged in",
        ),
    ] = False,
    browser: Annotated[
        bool,
        typer.Option(
            "--browser",
            "-b",
            help="Use browser-based OAuth login instead of email/password",
        ),
    ] = False,
) -> None:
    """
    Authenticate with AiCippy.

    Default: prompts for email and password (direct Cognito auth).
    With --browser/-b: opens the browser for OAuth PKCE login.

    Tokens are stored securely in the OS keychain (macOS Keychain,
    Windows Credential Manager, Linux SecretService). Falls back to
    encrypted file storage (~/.aicippy/credentials.enc) if the
    keychain is unavailable.

    Session expires after 60 minutes of inactivity.
    """
    with CorrelationContext() as ctx:
        logger.info(
            "login_started",
            method="browser" if browser else "password",
            correlation_id=ctx.correlation_id,
        )

        # First check for automation
        if not check_automation_and_verify():
            logger.warning("automation_blocked_at_login")
            raise typer.Exit(1)

        try:
            session_mgr = get_session_manager()

            # Check if already logged in with valid session
            if not force:
                validation = session_mgr.validate()
                if not validation.needs_login:
                    console.print(
                        Panel(
                            f"[green]Already logged in![/green]\n"
                            f"User: {validation.session.username}\n"
                            f"Email: {validation.session.email}\n"
                            f"Session expires in: {validation.session.minutes_remaining:.0f} minutes",
                            title="Session Active",
                            border_style="green",
                        )
                    )
                    console.print("Use --force to re-authenticate.")
                    return

            # Perform login using the selected method
            if browser:
                success = perform_browser_login()
            else:
                success = perform_interactive_login()

            if not success:
                raise typer.Exit(1)

            # Human verification after successful login
            console.print()  # Spacing
            if not perform_human_verification_after_login():
                logger.warning("human_verification_failed_at_login")
                console.print("[red]Human verification failed. Login cancelled.[/red]")
                # Invalidate the session since verification failed
                session_mgr.logout()
                raise typer.Exit(1)

        except typer.Exit:
            raise
        except Exception as e:
            logger.exception("login_failed", error=str(e))
            error_console.print(f"[red]Login error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def logout() -> None:
    """
    Log out from AiCippy and clear stored credentials.

    Removes tokens from the OS keychain and invalidates the session.
    """
    with CorrelationContext() as ctx:
        logger.info("logout_started", correlation_id=ctx.correlation_id)

        try:
            # Invalidate local session first
            session_mgr = get_session_manager()
            username, _ = session_mgr.get_user()

            session_success, session_msg = session_mgr.logout()

            # Try to logout from auth provider as well
            try:
                from aicippy.auth.cognito import CognitoAuth
                auth = CognitoAuth()
                auth.logout()
            except ImportError:
                logger.debug("auth_module_not_available_for_logout")
            except Exception as auth_logout_err:
                logger.debug("auth_provider_logout_failed", error=str(auth_logout_err))

            if session_success:
                console.print(
                    Panel(
                        f"[green]Successfully logged out![/green]\n"
                        f"Goodbye{', ' + username if username else ''}!",
                        title="Logged Out",
                        border_style="green",
                    )
                )
            else:
                console.print(f"[yellow]{session_msg}[/yellow]")

        except Exception as e:
            logger.exception("logout_failed", error=str(e))
            error_console.print(f"[red]Logout error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def init(
    path: Annotated[
        Path,
        typer.Argument(
            help="Directory to initialize (defaults to current directory)",
        ),
    ] = Path("."),
    force: Annotated[
        bool,
        typer.Option(
            "--force",
            "-f",
            help="Overwrite existing aicippy.md file",
        ),
    ] = False,
) -> None:
    """
    Initialize AiCippy in a project directory.

    Scans the repository, detects tech stack, and creates an aicippy.md
    file with project context for the AI agents.
    """
    from aicippy.knowledge.project_scanner import ProjectScanner

    with CorrelationContext() as ctx:
        logger.info("init_started", path=str(path), correlation_id=ctx.correlation_id)

        target_path = path.resolve()
        aicippy_md = target_path / "aicippy.md"

        if aicippy_md.exists() and not force:
            error_console.print(
                f"[yellow]aicippy.md already exists at {target_path}[/yellow]"
            )
            console.print("Use --force to overwrite.")
            raise typer.Exit(1)

        console.print(f"[blue]Scanning project at {target_path}...[/blue]")

        try:
            scanner = ProjectScanner(target_path)
            with console.status("[bold blue]Analyzing project structure..."):
                project_info = scanner.scan()

            # Write aicippy.md
            aicippy_md.write_text(project_info.to_markdown())

            console.print(
                Panel(
                    f"[green]Project initialized![/green]\n\n"
                    f"Created: {aicippy_md}\n"
                    f"Tech Stack: {', '.join(project_info.tech_stack)}\n"
                    f"Files Scanned: {project_info.files_count}",
                    title="Initialization Complete",
                    border_style="green",
                )
            )

        except Exception as e:
            logger.exception("init_failed", error=str(e))
            error_console.print(f"[red]Initialization error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def chat(
    message: Annotated[
        str,
        typer.Argument(
            help="Message to send to the AI assistant",
        ),
    ],
    model: Annotated[
        Optional[str],
        typer.Option(
            "--model",
            "-m",
            help="AI model to use (opus, sonnet, haiku, llama)",
        ),
    ] = None,
) -> None:
    """
    Send a single message to AiCippy and get a response.

    For interactive sessions, use the 'aicippy' command without arguments.
    """
    from aicippy.agents.orchestrator import AgentOrchestrator

    with CorrelationContext() as ctx:
        logger.info("chat_started", correlation_id=ctx.correlation_id)

        try:
            settings = get_settings()
            model_id = settings.get_model_id(model)

            console.print(f"[dim]Using model: {model or settings.default_model}[/dim]")

            orchestrator = AgentOrchestrator(model_id=model_id)

            with console.status("[bold blue]Thinking..."):
                response = asyncio.run(orchestrator.chat(message))

            console.print(Markdown(response.content))

            # Show token usage
            if response.usage:
                console.print(
                    f"\n[dim]Tokens: {response.usage.input_tokens} in / "
                    f"{response.usage.output_tokens} out[/dim]"
                )

        except Exception as e:
            logger.exception("chat_failed", error=str(e))
            error_console.print(f"[red]Chat error: {e}[/red]")
            raise typer.Exit(1)


@app.command(name="run")
def run_task(
    task: Annotated[
        str,
        typer.Argument(
            help="Task description for the agents to execute",
        ),
    ],
    agents: Annotated[
        int,
        typer.Option(
            "--agents",
            "-a",
            help="Number of parallel agents (1-10)",
            min=1,
            max=10,
        ),
    ] = 3,
    model: Annotated[
        Optional[str],
        typer.Option(
            "--model",
            "-m",
            help="AI model to use (opus, sonnet, haiku, llama)",
        ),
    ] = None,
) -> None:
    """
    Execute a task using multiple parallel agents.

    Spawns specialized agents that collaborate to complete the task.
    """
    from aicippy.agents.orchestrator import AgentOrchestrator

    with CorrelationContext() as ctx:
        logger.info(
            "run_task_started",
            task=task,
            agents=agents,
            correlation_id=ctx.correlation_id,
        )

        try:
            settings = get_settings()
            model_id = settings.get_model_id(model)

            console.print(
                Panel(
                    f"Task: {task}\n"
                    f"Agents: {agents}\n"
                    f"Model: {model or settings.default_model}",
                    title="Starting Task Execution",
                    border_style="blue",
                )
            )

            orchestrator = AgentOrchestrator(
                model_id=model_id,
                max_agents=agents,
            )

            # Run with progress display
            asyncio.run(orchestrator.run_task_with_progress(task, console))

        except Exception as e:
            logger.exception("run_task_failed", error=str(e))
            error_console.print(f"[red]Task execution error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def config(
    show: Annotated[
        bool,
        typer.Option(
            "--show",
            "-s",
            help="Show current configuration",
        ),
    ] = True,
    edit: Annotated[
        bool,
        typer.Option(
            "--edit",
            "-e",
            help="Open configuration in editor",
        ),
    ] = False,
) -> None:
    """
    Show or edit AiCippy configuration.

    Configuration is stored in ~/.aicippy/config.toml
    """
    settings = get_settings()

    if edit:
        import subprocess

        config_file = settings.local_config_dir / "config.toml"

        # Create default config if it doesn't exist
        if not config_file.exists():
            config_file.write_text(
                "# AiCippy Configuration\n"
                "# See documentation for available options\n\n"
                "[general]\n"
                f"default_model = \"{settings.default_model}\"\n"
                f"max_parallel_agents = {settings.max_parallel_agents}\n"
            )

        editor = os.environ.get("EDITOR", "nano")
        subprocess.run([editor, str(config_file)], check=False)
        return

    # Show configuration
    table = Table(title="AiCippy Configuration", border_style="blue")
    table.add_column("Setting", style="cyan")
    table.add_column("Value", style="green")

    table.add_row("AWS Account ID", settings.aws_account_id)
    table.add_row("AWS Region", settings.aws_region)
    table.add_row("Default Model", settings.default_model)
    table.add_row("Max Parallel Agents", str(settings.max_parallel_agents))
    table.add_row("Session TTL", f"{settings.session_ttl_minutes} minutes")
    table.add_row("Config Directory", str(settings.local_config_dir))
    table.add_row("WebSocket URL", settings.websocket_url)
    table.add_row("Cognito Domain", settings.cognito_domain)

    console.print(table)


@app.command()
def status() -> None:
    """
    Show current session and agent status.

    Displays active agents, connection status, and session information.
    """
    from aicippy.agents.status import get_agent_status

    with CorrelationContext():
        try:
            status_info = asyncio.run(get_agent_status())

            # Session info
            session_table = Table(title="Session Status", border_style="blue")
            session_table.add_column("Property", style="cyan")
            session_table.add_column("Value", style="green")

            session_table.add_row("Session ID", status_info.session_id or "Not started")
            session_table.add_row(
                "Connection",
                "[green]Connected[/green]" if status_info.connected else "[red]Disconnected[/red]",
            )
            session_table.add_row("Active Agents", str(status_info.active_agents))
            session_table.add_row("Total Tokens Used", f"{status_info.total_tokens:,}")

            console.print(session_table)

            # Agent list if any are active
            if status_info.agents:
                agent_table = Table(title="Active Agents", border_style="green")
                agent_table.add_column("ID", style="cyan")
                agent_table.add_column("Type", style="blue")
                agent_table.add_column("Status", style="yellow")
                agent_table.add_column("Progress")

                for agent in status_info.agents:
                    status_color = {
                        "running": "green",
                        "thinking": "yellow",
                        "error": "red",
                        "idle": "dim",
                    }.get(agent.status, "white")

                    agent_table.add_row(
                        agent.id,
                        agent.type,
                        f"[{status_color}]{agent.status}[/{status_color}]",
                        f"{agent.progress}%",
                    )

                console.print(agent_table)

        except Exception as e:
            logger.exception("status_failed", error=str(e))
            error_console.print(f"[red]Status error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def usage(
    detail: Annotated[
        bool,
        typer.Option(
            "--detail",
            "-d",
            help="Show detailed per-agent breakdown",
        ),
    ] = False,
) -> None:
    """
    Show token usage statistics.

    Displays usage for the current session and historical data.
    """
    from aicippy.agents.usage import get_usage_stats

    with CorrelationContext():
        try:
            stats = asyncio.run(get_usage_stats())

            # Summary table
            summary_table = Table(title="Token Usage Summary", border_style="blue")
            summary_table.add_column("Period", style="cyan")
            summary_table.add_column("Input Tokens", style="green", justify="right")
            summary_table.add_column("Output Tokens", style="green", justify="right")
            summary_table.add_column("Total", style="yellow", justify="right")

            summary_table.add_row(
                "Current Session",
                f"{stats.session_input:,}",
                f"{stats.session_output:,}",
                f"{stats.session_total:,}",
            )
            summary_table.add_row(
                "Today",
                f"{stats.today_input:,}",
                f"{stats.today_output:,}",
                f"{stats.today_total:,}",
            )
            summary_table.add_row(
                "This Month",
                f"{stats.month_input:,}",
                f"{stats.month_output:,}",
                f"{stats.month_total:,}",
            )

            console.print(summary_table)

            if detail and stats.per_agent:
                agent_table = Table(title="Per-Agent Breakdown", border_style="green")
                agent_table.add_column("Agent", style="cyan")
                agent_table.add_column("Model", style="blue")
                agent_table.add_column("Tokens", style="yellow", justify="right")

                for agent_stat in stats.per_agent:
                    agent_table.add_row(
                        agent_stat.agent_type,
                        agent_stat.model,
                        f"{agent_stat.total_tokens:,}",
                    )

                console.print(agent_table)

        except Exception as e:
            logger.exception("usage_failed", error=str(e))
            error_console.print(f"[red]Usage error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def upgrade(
    check_only: Annotated[
        bool,
        typer.Option(
            "--check",
            "-c",
            help="Only check for updates, don't install",
        ),
    ] = False,
) -> None:
    """
    Upgrade AiCippy to the latest version from PyPI.

    Checks for available updates and optionally installs them.
    """
    from aicippy.installer import upgrade_aicippy

    with console.status("[bold blue]Checking for updates..."):
        try:
            success, message, version_info = asyncio.run(
                upgrade_aicippy(console=console, check_only=check_only)
            )

            if check_only:
                if version_info.update_available:
                    console.print(
                        Panel(
                            f"[yellow]Update available![/yellow]\n\n"
                            f"Current version: {version_info.current}\n"
                            f"Latest version: {version_info.latest}\n\n"
                            f"Run [bold cyan]aicippy upgrade[/bold cyan] to update.",
                            title="Update Available",
                            border_style="yellow",
                        )
                    )
                else:
                    console.print(
                        Panel(
                            f"[green]You're up to date![/green]\n\n"
                            f"Current version: {version_info.current}",
                            title="No Updates Available",
                            border_style="green",
                        )
                    )
                return

            if success:
                console.print(
                    Panel(
                        f"[green]{message}[/green]",
                        title="Upgrade Complete",
                        border_style="green",
                    )
                )
                # Re-exec so the new code runs immediately
                import shutil
                aicippy_path = shutil.which("aicippy")
                if aicippy_path:
                    os.environ["_AICIPPY_UPDATED"] = "1"
                    os.execv(aicippy_path, ["aicippy"])
            else:
                error_console.print(f"[red]Upgrade failed: {message}[/red]")
                raise typer.Exit(1)

        except typer.Exit:
            raise
        except Exception as e:
            logger.exception("upgrade_failed", error=str(e))
            error_console.print(f"[red]Upgrade error: {e}[/red]")
            raise typer.Exit(1)


@app.command(name="help")
def show_help() -> None:
    """
    Show detailed help and usage information.
    """
    help_text = """
# AiCippy Help

## Commands

| Command | Description |
|---------|-------------|
| `aicippy` | Start interactive session |
| `aicippy login` | Authenticate with Cognito |
| `aicippy logout` | Clear credentials |
| `aicippy session` | View/manage session |
| `aicippy init` | Initialize project |
| `aicippy chat <msg>` | Single query mode |
| `aicippy run <task>` | Execute with agents |
| `aicippy config` | Show/edit config |
| `aicippy status` | Agent status |
| `aicippy usage` | Token usage |
| `aicippy upgrade` | Self-update |
| `aicippy update` | Self-update (alias) |
| `aicippy doctor` | Diagnose & fix issues |
| `aicippy install` | Run/verify installation |

## Session Management

Sessions expire after **60 minutes** of inactivity. When your session expires:
- You will be prompted to login again
- Use `aicippy session --refresh` to extend your session

## Interactive Commands

| Command | Description |
|---------|-------------|
| `/help` | Show all commands |
| `/model <name>` | Switch model |
| `/mode <name>` | Change mode |
| `/agents spawn <n>` | Spawn agents |
| `/agents list` | List agents |
| `/agents stop` | Stop agents |
| `/kb sync` | Sync to KB |
| `/tools list` | List tools |
| `/usage` | Token usage |
| `/clear` | Clear conversation |
| `/quit` | Exit |

## Environment Variables

- `AICIPPY_AWS_REGION` - AWS region (default: us-east-1)
- `AICIPPY_DEFAULT_MODEL` - Default model (opus/sonnet/haiku/llama)
- `AICIPPY_LOG_LEVEL` - Log level (DEBUG/INFO/WARNING/ERROR)
- `AICIPPY_HOME` - Installation directory
- `AICIPPY_CONFIG` - Configuration directory
- `AICIPPY_CACHE` - Cache directory

## Support

- Documentation: https://docs.aicippy.com
- Issues: https://github.com/aivibe/aicippy/issues
- Email: support@aivibe.in
"""
    console.print(Markdown(help_text))


@app.command()
def install(
    show_progress: Annotated[
        bool,
        typer.Option(
            "--progress",
            "-p",
            help="Show animated progress display",
        ),
    ] = True,
    verify_only: Annotated[
        bool,
        typer.Option(
            "--verify",
            "-v",
            help="Only verify installation, don't install",
        ),
    ] = False,
) -> None:
    """
    Run or verify AiCippy installation.

    Sets up directories, permissions, and environment variables.
    Use --verify to check existing installation without modifications.
    """
    from aicippy.installer import install_aicippy, verify_installation

    with CorrelationContext() as ctx:
        logger.info("install_started", correlation_id=ctx.correlation_id)

        try:
            if verify_only:
                console.print("[blue]Verifying installation...[/blue]")
                success, issues = verify_installation()

                if success:
                    console.print(
                        Panel(
                            "[green]Installation verified successfully![/green]\n\n"
                            "All components are properly configured.",
                            title="Verification Passed",
                            border_style="green",
                        )
                    )
                else:
                    console.print(
                        Panel(
                            "[yellow]Installation issues found:[/yellow]\n\n"
                            + "\n".join(f"• {issue}" for issue in issues),
                            title="Verification Issues",
                            border_style="yellow",
                        )
                    )
                    console.print("\nRun [cyan]aicippy install[/cyan] to fix issues.")
                    raise typer.Exit(1)
                return

            # Run installation (progress bar and completion message handled internally)
            result = asyncio.run(
                install_aicippy(console=console, show_progress=show_progress)
            )

            if not result.success:
                raise typer.Exit(1)

        except typer.Exit:
            raise
        except Exception as e:
            logger.exception("install_failed", error=str(e))
            error_console.print(f"[red]Installation error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def session(
    show: Annotated[
        bool,
        typer.Option(
            "--show",
            "-s",
            help="Show current session info",
        ),
    ] = True,
    refresh: Annotated[
        bool,
        typer.Option(
            "--refresh",
            "-r",
            help="Refresh session (extend timeout)",
        ),
    ] = False,
) -> None:
    """
    View or manage your AiCippy session.

    Sessions expire after 60 minutes of inactivity.
    Use --refresh to extend your session timeout.
    """
    with CorrelationContext() as ctx:
        logger.info("session_command", correlation_id=ctx.correlation_id)

        try:
            session_mgr = get_session_manager()
            validation = session_mgr.validate()

            if refresh:
                if validation.needs_login:
                    console.print("[yellow]No active session to refresh.[/yellow]")
                    console.print("Run [cyan]aicippy login[/cyan] to start a new session.")
                    return

                success, message = session_mgr.refresh()
                if success:
                    console.print("[green]Session refreshed![/green]")
                    console.print("Session extended for another 60 minutes.")
                else:
                    console.print(f"[yellow]Could not refresh: {message}[/yellow]")
                return

            # Show session info
            if validation.needs_login:
                console.print(
                    Panel(
                        f"[yellow]{validation.message}[/yellow]\n\n"
                        f"Status: {validation.status}\n\n"
                        f"Run [cyan]aicippy login[/cyan] to authenticate.",
                        title="No Active Session",
                        border_style="yellow",
                    )
                )
            else:
                session = validation.session
                minutes_remaining = session.minutes_remaining

                # Determine status color
                if minutes_remaining > 30:
                    status_style = "green"
                elif minutes_remaining > 10:
                    status_style = "yellow"
                else:
                    status_style = "red"

                session_table = Table.grid(padding=(0, 2))
                session_table.add_column(style="cyan")
                session_table.add_column(style="white")

                session_table.add_row("User:", session.username)
                session_table.add_row("Email:", session.email)
                session_table.add_row("Created:", session.created_at.strftime("%Y-%m-%d %H:%M UTC"))
                session_table.add_row("Last Activity:", session.last_activity.strftime("%Y-%m-%d %H:%M UTC"))
                session_table.add_row(
                    "Expires In:",
                    f"[{status_style}]{minutes_remaining:.0f} minutes[/{status_style}]"
                )
                session_table.add_row("Device ID:", session.device_id[:8] + "...")

                console.print(
                    Panel(
                        session_table,
                        title="Session Information",
                        border_style="blue",
                    )
                )

                if minutes_remaining < 10:
                    console.print(
                        "\n[yellow]Your session is expiring soon. "
                        "Run [cyan]aicippy session --refresh[/cyan] to extend.[/yellow]"
                    )

        except Exception as e:
            logger.exception("session_failed", error=str(e))
            error_console.print(f"[red]Session error: {e}[/red]")
            raise typer.Exit(1)


@app.command()
def doctor(
    fix: Annotated[
        bool,
        typer.Option(
            "--fix",
            "-f",
            help="Attempt to auto-fix issues found",
        ),
    ] = False,
) -> None:
    """
    Diagnose and fix AiCippy configuration issues.

    Checks AWS credentials, Cognito auth, Bedrock access, keychain,
    config integrity, dependencies, and network connectivity.
    Use --fix to attempt automatic repairs.
    """
    import importlib

    checks_passed = 0
    checks_failed = 0
    checks_fixed = 0

    def check(name: str, condition: bool, fix_fn: Callable[[], None] | None = None, fix_desc: str = "") -> bool:
        nonlocal checks_passed, checks_failed, checks_fixed
        if condition:
            checks_passed += 1
            console.print(f"  [green]PASS[/green]  {name}")
            return True
        if fix and fix_fn and callable(fix_fn):
            try:
                fix_fn()
                checks_fixed += 1
                console.print(f"  [yellow]FIXED[/yellow] {name} ({fix_desc})")
                return True
            except Exception as e:
                checks_failed += 1
                console.print(f"  [red]FAIL[/red]  {name} (fix failed: {e})")
                return False
        checks_failed += 1
        console.print(f"  [red]FAIL[/red]  {name}" + (f" [dim]({fix_desc})[/dim]" if fix_desc else ""))
        return False

    console.print(Panel("[bold]AiCippy Doctor[/bold] - System Diagnostics", border_style="blue"))
    console.print()

    # 1. Python & Dependencies
    console.print("[bold cyan]1. Core Dependencies[/bold cyan]")
    check("Python version >= 3.11", sys.version_info >= (3, 11))

    for pkg in ["typer", "rich", "boto3", "httpx", "jose", "keyring", "pydantic"]:
        try:
            importlib.import_module(pkg)
            check(f"Package: {pkg}", True)
        except ImportError:
            check(f"Package: {pkg}", False, fix_desc="pip install aicippy[all]")

    console.print()

    # 2. Configuration
    console.print("[bold cyan]2. Configuration[/bold cyan]")
    try:
        settings = get_settings()
        check("Settings loaded", True)
        check("AWS region configured", bool(settings.aws_region), fix_desc=f"region={settings.aws_region}")
        check("Cognito pool configured", bool(settings.cognito_user_pool_id))
        check("Cognito client configured", bool(settings.cognito_client_id))
    except Exception as e:
        check("Settings loaded", False, fix_desc=str(e))

    config_dir = Path.home() / ".aicippy"
    check(
        "Config directory exists",
        config_dir.exists(),
        fix_fn=lambda: config_dir.mkdir(parents=True, exist_ok=True),
        fix_desc="mkdir ~/.aicippy",
    )

    sessions_dir = config_dir / "sessions"
    check(
        "Sessions directory exists",
        sessions_dir.exists(),
        fix_fn=lambda: sessions_dir.mkdir(parents=True, exist_ok=True),
        fix_desc="mkdir ~/.aicippy/sessions",
    )

    history_dir = config_dir / "history"
    check(
        "History directory exists",
        history_dir.exists(),
        fix_fn=lambda: history_dir.mkdir(parents=True, exist_ok=True),
        fix_desc="mkdir ~/.aicippy/history",
    )

    console.print()

    # 3. AWS Credentials
    console.print("[bold cyan]3. AWS Credentials[/bold cyan]")
    try:
        import boto3

        sts = boto3.client("sts", region_name=settings.aws_region)
        identity = sts.get_caller_identity()
        account = identity.get("Account", "unknown")
        check("AWS credentials valid", True, fix_desc=f"account={account}")
    except Exception as e:
        check("AWS credentials valid", False, fix_desc="Configure AWS CLI: aws configure")

    console.print()

    # 4. Cognito Authentication
    console.print("[bold cyan]4. Cognito Authentication[/bold cyan]")
    try:
        from aicippy.auth.cognito import CognitoAuth

        auth = CognitoAuth()
        is_auth = auth.is_authenticated()
        check("Cognito auth initialized", True)
        check("User authenticated", is_auth, fix_desc="Run: aicippy login")
        if is_auth:
            tokens = auth.get_current_tokens()
            check("Tokens valid", tokens is not None and not tokens.is_expired())
    except Exception as e:
        check("Cognito auth initialized", False, fix_desc=str(e))

    console.print()

    # 5. Bedrock Access
    console.print("[bold cyan]5. Bedrock Model Access[/bold cyan]")
    try:
        import boto3

        bedrock = boto3.client("bedrock", region_name=settings.aws_region)
        models = bedrock.list_foundation_models(
            byProvider="meta",
        )
        model_count = len(models.get("modelSummaries", []))
        check("Bedrock API accessible", model_count > 0, fix_desc=f"{model_count} Meta models available")
    except Exception as e:
        check("Bedrock API accessible", False, fix_desc=str(e)[:80])

    try:
        client = boto3.client("bedrock-runtime", region_name=settings.aws_region)
        body = json.dumps({
            "prompt": "<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\nHi<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n",
            "max_gen_len": 10,
            "temperature": 0.1,
        })
        response = client.invoke_model(
            modelId=settings.model_llama_id,
            body=body,
            contentType="application/json",
            accept="application/json",
        )
        result = json.loads(response["body"].read())
        check("Llama 4 Maverick invocation", bool(result.get("generation", "")), fix_desc="Model responds OK")
    except Exception as e:
        check("Llama 4 Maverick invocation", False, fix_desc=str(e)[:80])

    console.print()

    # 6. Keychain Storage
    console.print("[bold cyan]6. Keychain Storage[/bold cyan]")
    try:
        from aicippy.auth.keychain import KeychainStorage

        ks = KeychainStorage()
        check("Keychain accessible", True, fix_desc=f"backend={ks._backend_name if hasattr(ks, '_backend_name') else 'default'}")
    except Exception as e:
        check("Keychain accessible", False, fix_desc=str(e)[:60])

    console.print()

    # 7. Network Connectivity
    console.print("[bold cyan]7. Network Connectivity[/bold cyan]")
    try:
        conn = socket.create_connection(("cognito-idp.us-east-1.amazonaws.com", 443), timeout=5)
        conn.close()
        check("Cognito endpoint reachable", True)
    except Exception:
        check("Cognito endpoint reachable", False, fix_desc="Check internet/VPN")

    try:
        conn = socket.create_connection(("bedrock-runtime.us-east-1.amazonaws.com", 443), timeout=5)
        conn.close()
        check("Bedrock endpoint reachable", True)
    except Exception:
        check("Bedrock endpoint reachable", False, fix_desc="Check internet/VPN")

    console.print()

    # 8. Session State
    console.print("[bold cyan]8. Session State[/bold cyan]")
    try:
        session_mgr = get_session_manager()
        validation = session_mgr.validate()
        check("Session manager initialized", True)
        check("Session active", not validation.needs_login, fix_desc="Run: aicippy login")
    except Exception as e:
        check("Session manager initialized", False, fix_desc=str(e)[:60])

    # Summary
    console.print()
    total = checks_passed + checks_failed + checks_fixed
    if checks_failed == 0:
        console.print(
            Panel(
                f"[green]All {total} checks passed![/green]"
                + (f"\n{checks_fixed} issues auto-fixed." if checks_fixed else "")
                + "\n\nAiCippy is healthy and ready to use.",
                title="Doctor Summary",
                border_style="green",
            )
        )
    else:
        console.print(
            Panel(
                f"[yellow]{checks_passed} passed, {checks_failed} failed"
                + (f", {checks_fixed} fixed" if checks_fixed else "")
                + f"[/yellow]\n\n"
                + ("Run [cyan]aicippy doctor --fix[/cyan] to attempt auto-repair." if not fix else "Some issues require manual intervention."),
                title="Doctor Summary",
                border_style="yellow",
            )
        )
        raise typer.Exit(1)


@app.command()
def update(
    check_only: Annotated[
        bool,
        typer.Option(
            "--check",
            "-c",
            help="Only check for updates, don't install",
        ),
    ] = False,
) -> None:
    """
    Update AiCippy to the latest version (alias for upgrade).
    """
    upgrade(check_only=check_only)


if __name__ == "__main__":
    app()
