# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
CLI Channel

Interactive command-line interface for the agent.
"""

import asyncio
import logging

from ..core.agent import Agent
from ..core.providers import get_available_providers
from .connect_wizard import ConnectMixin
from .formatting import FormatMode

logger = logging.getLogger(__name__)


class CLIChannel(ConnectMixin):
    """
    Command-line interface for the agent.

    Usage:
        agent = Agent()
        cli = CLIChannel(agent)
        cli.run()
    """

    format_mode = FormatMode.PLAIN
    supports_buttons = False
    supports_message_deletion = False

    def __init__(self, agent: Agent):
        self.agent = agent
        self.user_id = "cli_user"
        self.channel = "cli"
        self._wizard_state: dict[str, dict] = {}

    # ── WizardHost protocol implementation ──────────────────────────

    async def wizard_send(self, recipient_id: str, text: str) -> None:
        print(text)

    async def wizard_delete_message(self, recipient_id: str, message_ref: object) -> bool:
        return False

    # wizard_send_menu is inherited from ConnectMixin (numbered text menus)

    # ── CLI helpers ─────────────────────────────────────────────────

    def _run_async(self, coro):
        loop = asyncio.new_event_loop()
        try:
            loop.run_until_complete(coro)
        finally:
            loop.close()

    def print_header(self):
        """Print welcome header."""
        status = self.agent.get_status()

        router_info = ""
        if status.get("model_router") == "active":
            router_info = " | Router: active"

        print("=" * 60)
        print(f"  {status['name']}")
        print(f"  Model: {status['provider']}{router_info}")
        print(
            f"  Memory: {status['memory_entries']} entries | "
            f"Skills: {status['skills_loaded']} | "
            f"Tools: {status['tools_available']}"
        )
        print("=" * 60)
        print()
        print("Commands:")
        print("  /quit, /exit     - Exit")
        print("  /clear           - Clear conversation")
        print("  /status          - System status")
        print("  /creature        - Creature stats")
        print("  /model <name>    - Switch model")
        print("  /models          - List available models")
        print("  /start           - Guided setup (tarot spread)")
        print("  /tutorial        - Replay onboarding walkthrough")
        print("  /connect <svc>   - Connect provider (anthropic, openai, ollama)")
        print("  /remember <k> <v>- Store memory")
        print("  /recall <query>  - Search memory")
        print("  /skills          - List skills")
        print("  /tools           - List tools")
        print()

    def handle_command(self, command: str) -> bool:
        """
        Handle a slash command.

        Returns True if command was handled, False otherwise.
        """
        parts = command[1:].split(maxsplit=1)
        cmd = parts[0].lower()
        args = parts[1] if len(parts) > 1 else ""

        if cmd in ["quit", "exit", "q"]:
            print("Goodbye!")
            return None  # Signal to exit

        elif cmd == "clear":
            self.agent.clear_history(self.user_id, self.channel)
            print("✓ Conversation cleared")
            return True

        elif cmd == "trust":
            from ..core.security import TrustLevel

            session = self.agent.sessions.get_or_create_session(self.user_id, self.channel)
            levels = list(TrustLevel)
            for lvl in levels:
                marker = (
                    "➡️ "
                    if session.trust_level == lvl
                    else ("✅ " if levels.index(session.trust_level) > levels.index(lvl) else "   ")
                )
                print(f"  {marker}{lvl.value.upper()}")
            if session.trust_level == TrustLevel.STRANGER:
                progress = f"{session.positive_interactions}/10 to KNOWN"
            elif session.trust_level == TrustLevel.KNOWN:
                progress = f"{session.positive_interactions}/50 to TRUSTED"
            else:
                progress = "Maximum level"
            print(f"  Score: {session.trust_score:.1f}  Progress: {progress}")
            return True

        elif cmd == "budget":
            session = self.agent.sessions.get_or_create_session(self.user_id, self.channel)
            pct = (
                min(100, session.spent_today / session.daily_budget * 100)
                if session.daily_budget > 0
                else 0
            )
            bar = f"[{'█' * int(pct / 10)}{'░' * (10 - int(pct / 10))}]"
            print(f"  Daily Limit:  ${session.daily_budget:.2f}")
            print(f"  {bar} {pct:.1f}%")
            print(f"  Spent:        ${session.spent_today:.4f}")
            print(f"  Remaining:    ${session.remaining_budget:.4f}")
            return True

        elif cmd == "caps":
            session = self.agent.sessions.get_or_create_session(self.user_id, self.channel)
            caps = sorted([c.value for c in session.capabilities])
            if caps:
                for c in caps:
                    print(f"  ✅ {c}")
            else:
                print("  (no capabilities yet)")
            return True

        elif cmd == "status":
            from ..core.tools import get_tool_registry

            tools = get_tool_registry()
            info = tools.execute("get_system_info", {})

            status = self.agent.get_status()
            print("\n📊 Status")
            print(f"Model: {status['provider']}")
            print(f"Security: {status['security_mode']}")
            print(f"Router: {status.get('model_router', 'inactive')}")
            print(f"Memory: {status['memory_entries']} entries")
            print(f"Skills: {status['skills_loaded']}")
            print(
                f"Tools: {status['tools_available']} available ({status.get('tools_registered', '?')} registered)"
            )
            print(f"Tasks: {status['scheduled_tasks']}")
            print(f"\n{info}")
            return True

        elif cmd == "model":
            if not args:
                print(f"Current: {self.agent.provider.name}")
                print("Usage: /model <provider>")
                return True

            result = self.agent.switch_provider(args)
            print(f"✓ {result}")
            return True

        elif cmd == "models":
            providers = get_available_providers()
            print(f"Current: {self.agent.provider.name}")
            print(f"Available: {', '.join(sorted(set(providers)))}")
            return True

        elif cmd == "start":
            self._run_async(self.handle_start_command(self.user_id))
            return True

        elif cmd == "tutorial":
            self._run_async(self.handle_tutorial_command(self.user_id))
            return True

        elif cmd == "connect":
            self._handle_connect(args)
            return True

        elif cmd == "browse":
            self._run_async(self.handle_browse_command(self.user_id))
            return True

        elif cmd == "search":
            search_args = args.split() if args else []
            self._run_async(self.handle_search_command(self.user_id, search_args))
            return True

        elif cmd == "remember":
            parts = args.split(maxsplit=1)
            if len(parts) < 2:
                print("Usage: /remember <key> <value>")
                return True

            key, value = parts
            self.agent.remember(key, value)
            print(f"✓ Remembered: {key}")
            return True

        elif cmd == "recall":
            if not args:
                print("Usage: /recall <query>")
                return True

            results = self.agent.recall(args)
            if not results:
                print(f"No memories matching '{args}'")
            else:
                for entry in results[:10]:
                    print(f"  • {entry.key}: {entry.value} [{entry.category}]")
            return True

        elif cmd == "skills":
            skills = self.agent.skills.list_skills()
            if not skills:
                print("No skills loaded")
            else:
                for s in skills:
                    status = "✓" if s["enabled"] else "✗"
                    tools = ", ".join(s["tools"]) if s["tools"] else "no tools"
                    print(f"  {status} {s['name']}: {tools}")
            return True

        elif cmd == "tools":
            tools = self.agent.tools.get_all()
            categories = {}
            for t in tools:
                if t.category not in categories:
                    categories[t.category] = []
                categories[t.category].append(t.name)

            for cat, names in sorted(categories.items()):
                print(f"  [{cat}]")
                for name in sorted(names):
                    print(f"    • {name}")
            return True

        elif cmd == "creature":
            self._show_creature_stats()
            return True

        elif cmd == "help":
            self.print_header()
            print("  /trust       - Trust level info")
            print("  /budget      - Spending status")
            print("  /caps        - Your capabilities")
            print("  /status      - Full system status")
            print("  /creature    - Creature stats")
            print("  /model [x]   - Show/switch provider")
            print("  /remember    - Store a memory")
            print("  /recall      - Search memories")
            print("  /connect     - Connect services")
            print("  /browse      - Browse connected services")
            print("  /search      - Search a service")
            print("  /clear       - Clear history")
            print("  /quit        - Exit")
            return True

        else:
            print(f"Unknown command: /{cmd}")
            print("Type /help for available commands")
            return True

    def _handle_connect(self, args: str):
        """Handle /connect command via ConnectMixin."""
        cmd_parts = args.strip().split() if args.strip() else []
        self._run_async(self.handle_connect_command(self.user_id, cmd_parts))

    def _init_creature(self):
        """Load or create the creature on first run."""
        from datetime import datetime

        from ..creature import (
            CreatureDisplay,
            load_creature,
            run_creature_intro,
            save_creature,
        )

        creature = load_creature()
        if creature is None:
            result = run_creature_intro()
            if result is None:
                # Consent refused — use a bare default
                from ..creature import CreatureState

                creature = CreatureState(name="Familiar")
            else:
                creature = result.creature
        creature.last_seen = datetime.now().isoformat()
        save_creature(creature)
        self._creature = creature
        self._creature_display = CreatureDisplay(creature)

    def _check_evolution(self):
        """Check if the creature should evolve based on interaction count."""
        if not hasattr(self, "_creature") or self._creature is None:
            return

        from datetime import datetime

        from ..core.constitution import get_relationship_stage
        from ..creature import save_creature

        tracker = getattr(self.agent, "_interaction_tracker", None)
        if not tracker:
            return

        count = tracker.get_count(self.user_id)
        stage = get_relationship_stage(count)

        stage_map = {"stage_1": "baby", "stage_2": "juvenile", "stage_3": "adult"}
        new_evo = stage_map[stage.value]

        if new_evo != self._creature.evolution_stage:
            old = self._creature.evolution_stage
            self._creature.evolution_stage = new_evo
            self._creature.last_evolution_at = datetime.now().isoformat()
            save_creature(self._creature)
            self._creature_display.show_evolution(old, new_evo)

        # Sync total_interactions for display
        self._creature.total_interactions = count

    def _show_creature_stats(self):
        """Display creature stats for /creature command."""
        if not hasattr(self, "_creature") or self._creature is None:
            print("  No creature found. Restart to create one.")
            return

        from ..creature.state import ActivityState

        c = self._creature
        print()

        # Show the creature
        self._creature_display.show(ActivityState.IDLE)

        # Stats
        print(f"  Name:         {c.name}")
        print(f"  Species:      {c.species}")
        print(f"  Stage:        {c.evolution_stage}")
        print(f"  Interactions: {c.total_interactions}")
        if c.born_at:
            born = c.born_at[:10]
            print(f"  Born:         {born}")
        print(f"  Body:         {c.color_body}")
        print(f"  Eyes:         {c.color_eyes}")
        print(f"  Accent:       {c.color_accent}")

    def run(self):
        """Run the interactive CLI."""
        # Creature: load or create on first run
        self._init_creature()

        self.print_header()

        # CLI = physical access = owner trust
        # (Multi-user channels like Telegram start at STRANGER)
        from ..core.security import TrustLevel

        session = self.agent.sessions.get_or_create_session(self.user_id, self.channel)
        if session.trust_level != TrustLevel.OWNER:
            session.set_trust_level(TrustLevel.OWNER)
            self.agent.sessions.save_session(session)

        # Start scheduler
        self.agent.start_scheduler()

        # Wire scheduler delivery → terminal
        if self.agent.scheduler:

            def _deliver_to_cli(message, channel, chat_id):
                print(f"\n📬 [Scheduled] {message}\n")

            self.agent.scheduler.set_delivery_callback(_deliver_to_cli)

        # Show creature idle before first prompt
        if hasattr(self, "_creature_display") and self._creature_display:
            from ..creature.state import ActivityState as _AS

            self._creature_display.show(_AS.IDLE)
            print()

        while True:
            try:
                # Get input
                user_input = input("You: ").strip()

                if not user_input:
                    continue

                # Check for commands
                if user_input.startswith("/"):
                    result = self.handle_command(user_input)
                    if result is None:  # Exit signal
                        break
                    print()
                    continue

                # Intercept wizard input if wizard is active
                self._ensure_wizard_state()
                if self._wizard_state.get(self.user_id):
                    consumed = asyncio.new_event_loop().run_until_complete(
                        self.handle_wizard_message(self.user_id, user_input)
                    )
                    if consumed:
                        print()
                        continue

                # Show creature thinking while agent works
                if hasattr(self, "_creature_display") and self._creature_display:
                    from ..creature.state import ActivityState as _AS

                    self._creature_display.show(_AS.THINKING, "Thinking...")
                    print()

                # Process with agent (streaming when available)
                print("Agent: ", end="", flush=True)
                response = None
                _used_streaming = False
                try:
                    from familiar.core.providers import StreamEventType

                    stream_gen = self.agent.chat_stream(
                        user_input, user_id=self.user_id, channel=self.channel
                    )
                    final_text = ""
                    # Manual iteration to capture generator return value (sentinels)
                    try:
                        while True:
                            event = next(stream_gen)
                            if event.type == StreamEventType.TEXT:
                                print(event.content, end="", flush=True)
                                final_text += event.content
                            elif event.type == StreamEventType.TOOL_START:
                                print(
                                    f"\n[Using {event.tool_name}...]", end="", flush=True
                                )
                            elif event.type == StreamEventType.TOOL_END:
                                print(" done.", flush=True)
                            elif event.type == StreamEventType.ERROR:
                                final_text = event.error or "(error)"
                    except StopIteration as si:
                        # Generator return value carries sentinel/final text
                        if si.value:
                            response = si.value
                        else:
                            response = final_text or "(no response)"
                    _used_streaming = True
                except Exception:
                    # Fallback to non-streaming on any error
                    if response is None:
                        response = self.agent.chat(
                            user_input, user_id=self.user_id, channel=self.channel
                        )

                # Phase 2: intercept confirmation sentinel
                from familiar.core.confirmations import (
                    CALENDAR_MENU_PREFIX,
                    EMAIL_MENU_PREFIX,
                    SENTINEL_PREFIX,
                )

                if isinstance(response, str) and response.startswith(SENTINEL_PREFIX):
                    token = response[len(SENTINEL_PREFIX) :]
                    session = self.agent.sessions.get_or_create_session(self.user_id, self.channel)
                    pending = session.pending_confirmations.get(token)
                    if pending:
                        preview = pending.get("preview", "This action is ready to execute.")
                        risk = pending.get("risk", "medium")
                        flag = "⚠️  " if risk == "high" else ""
                        print(flag + preview)
                        print()
                        try:
                            answer = input("Confirm? [y/N] ").strip().lower()
                        except (EOFError, KeyboardInterrupt):
                            answer = "n"
                        if answer in ("y", "yes"):
                            tool_input = dict(pending["tool_input"])
                            tool_input["_confirmed"] = True
                            del session.pending_confirmations[token]
                            self.agent.sessions.save_session(session)
                            try:
                                result = self.agent.tools.execute(
                                    pending["tool_name"],
                                    tool_input,
                                    context={
                                        "session": session,
                                        "session_manager": self.agent.sessions,
                                        "agent": self.agent,
                                        "user_id": self.user_id,
                                        "channel": self.channel,
                                    },
                                )
                                try:
                                    from familiar.core.agent import _capture_context_from_result

                                    _capture_context_from_result(
                                        pending["tool_name"],
                                        pending.get("tool_input", {}),
                                        result,
                                        session,
                                        self.agent.sessions,
                                    )
                                except Exception as e:
                                    logger.debug("Error suppressed: %s", e)
                                print(result or "✅ Done.")
                            except Exception as e:
                                print(f"❌ Action failed: {e}")
                        else:
                            session.pending_confirmations.pop(token, None)
                            self.agent.sessions.save_session(session)
                            print("Cancelled.")
                    else:
                        print("⚠️  Confirmation expired. Please try again.")
                elif isinstance(response, str) and response.startswith(EMAIL_MENU_PREFIX):
                    session = self.agent.sessions.get_or_create_session(self.user_id, self.channel)
                    email_menu = session.session_context.get("email_menu", {})
                    summaries = email_menu.get("summaries", [])
                    results = email_menu.get("results", {})
                    if summaries:
                        print("\n📧 Email Accounts:\n")
                        for i, (key, label, summary) in enumerate(summaries, 1):
                            emoji = "📭" if summary == "0 unread" else "📬"
                            print(f"  {i}. {emoji} {label}: {summary}")
                        print("\n  Enter number to view, or press Enter for all:")
                        try:
                            choice = input("> ").strip()
                            if choice and choice.isdigit():
                                idx = int(choice) - 1
                                if 0 <= idx < len(summaries):
                                    print(results.get(summaries[idx][0], "No data"))
                                else:
                                    print("Invalid selection.")
                            else:
                                print("\n\n".join(results.values()))
                        except (EOFError, KeyboardInterrupt):
                            pass
                    else:
                        print("⚠️ No email accounts found.")
                elif isinstance(response, str) and response.startswith(CALENDAR_MENU_PREFIX):
                    session = self.agent.sessions.get_or_create_session(self.user_id, self.channel)
                    cal_menu = session.session_context.get("calendar_menu", {})
                    summaries = cal_menu.get("summaries", [])
                    results = cal_menu.get("results", {})
                    if summaries:
                        print("\n📅 Calendar Accounts:\n")
                        for i, (key, label, summary) in enumerate(summaries, 1):
                            emoji = "📭" if summary == "0 events" else "📆"
                            print(f"  {i}. {emoji} {label}: {summary}")
                        print("\n  Enter number to view, or press Enter for all:")
                        try:
                            choice = input("> ").strip()
                            if choice and choice.isdigit():
                                idx = int(choice) - 1
                                if 0 <= idx < len(summaries):
                                    print(results.get(summaries[idx][0], "No data"))
                                else:
                                    print("Invalid selection.")
                            else:
                                print("\n\n".join(results.values()))
                        except (EOFError, KeyboardInterrupt):
                            pass
                    else:
                        print("⚠️ No calendar accounts found.")
                else:
                    if not _used_streaming:
                        print(response)
                print()

                # Check for evolution after each interaction
                self._check_evolution()

                # Show creature idle after response
                if hasattr(self, "_creature_display") and self._creature_display:
                    from ..creature.state import ActivityState as _AS

                    self._creature_display.show(_AS.IDLE)
                    print()

            except KeyboardInterrupt:
                print("\nGoodbye!")
                break

            except EOFError:
                print("\nGoodbye!")
                break

            except Exception as e:
                print(f"\n❌ Error: {e}\n")

        # Cleanup
        self.agent.stop_scheduler()


def run_cli(agent: Agent = None):
    """Convenience function to run CLI."""
    if agent is None:
        from ..core.agent import create_agent

        agent = create_agent()

    cli = CLIChannel(agent)
    cli.run()
