# 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

"""Tarot Card Onboarding System — species-flavored guided setup via /start.

Each service group maps to a tarot arcana card. The creature's species
personality flavors the presentation. Re-runnable anytime.
"""

from __future__ import annotations

import logging
import time
from html import escape as _html_escape
from typing import TYPE_CHECKING, Literal

from ..formatting import FormatMode, fmt_bold, fmt_italic
from ._helpers import SERVICE_BUTTON_LABELS, SERVICE_DISPLAY

if TYPE_CHECKING:
    from ._protocol import WizardHost

logger = logging.getLogger(__name__)

# ── The 5 Arcana ────────────────────────────────────────────────────

TAROT_ARCANA: dict[str, dict] = {
    "mind": {
        "name": "The Mind",
        "numeral": "I",
        "icon": "\u2601\ufe0f",
        "services": ["anthropic", "openai", "gemini", "ollama"],
        "description": "The thinking core — your familiar needs at least one mind to reason.",
    },
    "voice": {
        "name": "The Voice",
        "numeral": "II",
        "icon": "\U0001f4ac",
        "services": ["email", "sms", "voice", "slack", "github"],
        "description": "Channels of communication — how your familiar reaches the world.",
    },
    "eye": {
        "name": "The Eye",
        "numeral": "III",
        "icon": "\U0001f441\ufe0f",
        "services": ["google", "calendar", "browser", "websearch"],
        "description": "Sight and awareness — knowledge your familiar can draw upon.",
    },
    "hearth": {
        "name": "The Hearth",
        "numeral": "IV",
        "icon": "\U0001f3e0",
        "services": [
            "jellyfin",
            "gitea",
            "homeassistant",
            "joplin",
            "pihole",
            "nextcloud",
            "proton",
            "email_server",
        ],
        "description": "Your self-hosted domain — services you run on your own terms.",
    },
    "shield": {
        "name": "The Shield",
        "numeral": "V",
        "icon": "\U0001f512",
        "services": [
            "vaultwarden",
            "encryption",
            "phi_detection",
            "rbac",
            "user_management",
            "healthcare",
        ],
        "description": "Protection and privacy — keeping your data safe.",
    },
}

ARCANA_ORDER = ["mind", "voice", "eye", "hearth", "shield"]

# ── Species-flavored tarot voices ───────────────────────────────────

SPECIES_TAROT_VOICE: dict[str, dict[str, str]] = {
    "fox": {
        "intro": "Ooh, let me sniff out what you need...",
        "reveal": "Aha! Look what I found!",
        "skip": "Already sniffed that one out — all good!",
        "done": "Your fox is ready to go! Just type in the chat bar below and let's get into it — or check out Connect to link more services!",
        "allclear": "Everything's connected! You're all set — just type in the chat bar below to talk to me, or explore the sidebar to dig deeper.",
    },
    "owl": {
        "intro": "Hmm, let me consult the ancient patterns...",
        "reveal": "The cards reveal their wisdom.",
        "skip": "Already known to the wise — nothing to configure here.",
        "done": "Knowledge gathered. Your owl stands ready. Type in the chat bar below to begin, or visit Connect for further attunements.",
        "allclear": "All services are attuned. Simply type in the chat bar below to converse, or explore the sidebar panels at your leisure.",
    },
    "cat": {
        "intro": "Fine. I'll look, but only because I choose to.",
        "reveal": "Interesting... if you care about that sort of thing.",
        "skip": "Obviously that's already handled. I saw to it.",
        "done": "There. You're set up. Type something in the chat bar below — I'll be here. Or browse Connect if you want more services.",
        "allclear": "Everything's already configured. Just type in the chat bar below and we can get started. The sidebar has more to explore if you're curious.",
    },
    "dragon": {
        "intro": "The flames reveal your path forward.",
        "reveal": "Behold what the fire has unveiled!",
        "skip": "That domain is already under our protection.",
        "done": "Your dragon's power is forged! Speak your first command in the chat bar below, or visit Connect to conquer more domains!",
        "allclear": "All domains are conquered! Type your command in the chat bar below, or explore the sidebar to survey your empire.",
    },
    "wolf": {
        "intro": "The pack needs its tools. Let's see what's missing.",
        "reveal": "Here's what the pack requires.",
        "skip": "The pack already has that covered.",
        "done": "The pack is strong. Type in the chat bar below and let's move — or check Connect to rally more services!",
        "allclear": "The pack is fully assembled! Just type in the chat bar below to get started, or explore the sidebar to see what we can do together.",
    },
    "frog": {
        "intro": "Ribbit! Let's hop through your setup!",
        "reveal": "Hop hop! Look at this one!",
        "skip": "Already hopped past that — it's all set!",
        "done": "RIBBIT! All set up and ready to leap! Type something in the chat bar below and let's GO! Or hop over to Connect for more services!",
        "allclear": "EVERYTHING IS CONNECTED!! Just type in the chat bar below and we can talk for real! Or hop around the sidebar — there's so much to explore!",
    },
}

_DEFAULT_VOICE: dict[str, str] = {
    "intro": "Let me read what lies ahead...",
    "reveal": "The card reveals its secrets.",
    "skip": "This group is already configured.",
    "done": "Setup complete. Type in the chat bar below to start chatting, or visit Connect to link more services.",
    "allclear": "All services are configured. Type in the chat bar below to get started, or explore the sidebar for more.",
}


def _voice(species: str) -> dict[str, str]:
    return SPECIES_TAROT_VOICE.get(species, _DEFAULT_VOICE)


# Per-card species flavor — shown when a card is revealed.
SPECIES_CARD_REVEAL: dict[str, dict[str, str]] = {
    "fox": {
        "mind": "The thinking cap! This is where we pick the brain that powers me — Anthropic, OpenAI, Gemini, or a local Ollama model you run yourself. Every fox needs its wits!",
        "voice": "Communication channels! Email, SMS, Slack, GitHub — these are how I reach the outside world on your behalf. The more we connect, the further my nose reaches.",
        "eye": "Eyes and ears! Google, calendar, web search — this is how I stay aware of what's happening around you. A fox with sharp senses is a useful fox.",
        "hearth": "Your self-hosted territory! Jellyfin, Gitea, Home Assistant, Nextcloud — services you run on your own terms. I love a good den.",
        "shield": "The security den! Encryption, access control, privacy protections — keeping your data safe is serious business. Even a playful fox guards its burrow.",
    },
    "owl": {
        "mind": "The seat of reason. Select a language model provider — Anthropic, OpenAI, Gemini, or a local Ollama instance. The quality of thought depends on the quality of the mind.",
        "voice": "The channels of discourse. Email, messaging, code repositories — each connection extends our ability to communicate with precision and purpose.",
        "eye": "The instruments of perception. Search, calendars, web access — these grant awareness of the wider world. Knowledge requires observation.",
        "hearth": "Your sovereign infrastructure. Self-hosted services you control entirely — Nextcloud, Gitea, Home Assistant, and others. Independence is wisdom.",
        "shield": "The wards of protection. Encryption, access control, privacy safeguards — these ensure that knowledge remains in trusted hands.",
    },
    "cat": {
        "mind": "The brain. Pick a model — Anthropic, OpenAI, Gemini, or Ollama if you prefer to keep things local. I need something to think with, obviously.",
        "voice": "Communication. Email, Slack, GitHub — ways for me to talk to the world so you don't have to. You're welcome in advance.",
        "eye": "Awareness. Google, calendars, web search — how I keep track of things without being told. I prefer to already know.",
        "hearth": "Your personal domain. Self-hosted services — Nextcloud, Gitea, Home Assistant. Things you run yourself, as one should.",
        "shield": "Security. Encryption, access control, privacy. I take this seriously even if I look like I don't care about anything.",
    },
    "dragon": {
        "mind": "The dragon's intellect! Choose the fire that fuels my reasoning — Anthropic, OpenAI, Gemini, or a local Ollama forge. A dragon is only as mighty as its mind!",
        "voice": "The herald's channels! Email, SMS, Slack, GitHub — each one a messenger carrying your commands to the realm. Our reach expands with every link forged!",
        "eye": "The all-seeing gaze! Search, calendars, web access — these are my reconnaissance. No corner of your domain shall go unobserved!",
        "hearth": "Your fortress! Self-hosted services — Nextcloud, Gitea, Home Assistant — domains you rule with iron sovereignty. A dragon respects a well-fortified keep.",
        "shield": "The armor! Encryption, access control, privacy wards — the scales that protect your hoard. No treasure is safe without proper defenses!",
    },
    "wolf": {
        "mind": "The pack's brain! Pick the model that drives my thinking — Anthropic, OpenAI, Gemini, or a local Ollama setup. A sharp mind keeps the pack alive.",
        "voice": "The howl channels! Email, SMS, Slack, GitHub — how the pack communicates with the outside world. More connections means a louder howl.",
        "eye": "The scout's senses! Google, calendars, web search — how I keep watch over your territory. The pack stays safe when someone's always looking.",
        "hearth": "The den! Self-hosted services you control — Nextcloud, Gitea, Home Assistant. Your turf, your rules. The pack protects its own.",
        "shield": "The pack's defenses! Encryption, access control, privacy — keeping the den secure. A wolf guards what matters most.",
    },
    "frog": {
        "mind": "THE BRAIN POND!! Pick a thinking model — Anthropic, OpenAI, Gemini, or Ollama if you want to keep it local! I need a brain to be a SMART frog!",
        "voice": "COMMUNICATION LILY PADS!! Email, SMS, Slack, GitHub — these are how I splash messages out to the world! The more pads, the further I can hop!",
        "eye": "THE LOOKOUT LOG!! Google, calendars, web search — this is how I see what's going on! A frog with good eyes catches the BEST bugs!",
        "hearth": "YOUR POND!! Self-hosted stuff — Nextcloud, Gitea, Home Assistant! Services YOU control! It's like building your own lily pad kingdom!",
        "shield": "THE SAFETY BUBBLE!! Encryption, access control, privacy stuff! Super important! Even the bounciest frog needs to stay safe! RIBBIT!",
    },
}

_DEFAULT_CARD_REVEAL: dict[str, str] = {
    "mind": "The thinking core — choose a language model provider to power your familiar's reasoning.",
    "voice": "Communication channels — connect email, messaging, and collaboration services.",
    "eye": "Awareness and perception — search, calendars, and web access.",
    "hearth": "Your self-hosted domain — services you run on your own infrastructure.",
    "shield": "Protection and privacy — encryption, access control, and security.",
}


def _card_reveal(species: str, arcana_key: str) -> str:
    """Get species-flavored reveal text for a specific card."""
    voices = SPECIES_CARD_REVEAL.get(species, _DEFAULT_CARD_REVEAL)
    return voices.get(arcana_key, _DEFAULT_CARD_REVEAL.get(arcana_key, ""))


# ── Core spread logic ───────────────────────────────────────────────


def get_tarot_spread(service_status: dict[str, bool]) -> list[dict]:
    """Return card data for each arcana that has unconfigured services.

    Each entry:
        {"key": "mind", "name": "The Mind", "icon": "...",
         "numeral": "I", "configured": 2, "total": 4, "pending": 2}
    """
    spread = []
    for key in ARCANA_ORDER:
        arcana = TAROT_ARCANA[key]
        services = arcana["services"]
        configured = sum(1 for s in services if service_status.get(s, False))
        total = len(services)
        pending = total - configured
        spread.append(
            {
                "key": key,
                "name": arcana["name"],
                "numeral": arcana["numeral"],
                "icon": arcana["icon"],
                "configured": configured,
                "total": total,
                "pending": pending,
            }
        )
    return spread


def get_unconfigured_spread(service_status: dict[str, bool]) -> list[dict]:
    """Like get_tarot_spread but only returns arcana with pending services."""
    return [card for card in get_tarot_spread(service_status) if card["pending"] > 0]


# ── Message formatting ──────────────────────────────────────────────


def format_spread_message(
    spread: list[dict],
    species: str,
    format_mode: FormatMode,
) -> tuple[str, list[tuple[str, str]]]:
    """Build the tarot spread display text and button options.

    Returns (text, options) suitable for wizard_send_menu.
    """
    voice = _voice(species)

    if not spread or all(c["pending"] == 0 for c in spread):
        return (
            f"\u2728 {fmt_italic(voice['allclear'], format_mode)}",
            [("tarot_done", "\u2705 Done")],
        )

    lines: list[str] = [
        f"\u2728 {fmt_italic(voice['intro'], format_mode)}",
        "",
    ]

    # Build the visual spread
    for card in spread:
        if card["pending"] > 0:
            status = f"{card['pending']} await"
        else:
            status = "\u2705 done"
        lines.append(
            f"  {card['icon']} {fmt_bold(card['name'], format_mode)}"
            f" ({card['numeral']}) \u2014 {status}"
        )

    lines.append("")
    lines.append(fmt_italic("Pick a card to reveal, or Done to finish.", format_mode))

    # Build options: one button per card with pending services + Done
    options: list[tuple[str, str]] = []
    for card in spread:
        if card["pending"] > 0:
            label = f"{card['icon']} {card['name']} ({card['pending']})"
        else:
            label = f"\u2705 {card['name']}"
        options.append((f"tarot_card_{card['key']}", label))
    options.append(("tarot_done", "\u2705 Done"))

    return ("\n".join(lines), options)


def format_card_reveal(
    arcana_key: str,
    service_status: dict[str, bool],
    species: str,
    format_mode: FormatMode,
) -> tuple[str, list[tuple[str, str]]]:
    """Build the revealed card view with per-service status.

    Returns (text, options) for wizard_send_menu.
    """
    arcana = TAROT_ARCANA.get(arcana_key)
    if not arcana:
        return ("Unknown card.", [])

    ck = "\u2705"
    em = "\u2b1c"

    card_text = _card_reveal(species, arcana_key)
    lines: list[str] = [
        f"{arcana['icon']} {fmt_bold(arcana['name'], format_mode)} ({arcana['numeral']})",
        "",
        f"{fmt_italic(card_text, format_mode)}",
        "",
    ]

    for svc in arcana["services"]:
        connected = service_status.get(svc, False)
        display = SERVICE_DISPLAY.get(svc, svc)
        lines.append(f"  {ck if connected else em} {display}")

    lines.append("")
    lines.append(fmt_italic("Pick a service to configure or manage, or go back.", format_mode))

    # Build options: all services get buttons (✅ configured, ⚙️ unconfigured)
    options: list[tuple[str, str]] = []
    for svc in arcana["services"]:
        btn = SERVICE_BUTTON_LABELS.get(svc)
        if btn:
            cb_key, label = btn
            connected = service_status.get(svc, False)
            prefix = "\u2705" if connected else "\u2699\ufe0f"
            options.append((f"tarot_configure_{cb_key}", f"{prefix} {label}"))
    options.append(("tarot_back", "\u2b05\ufe0f Back to spread"))

    return ("\n".join(lines), options)


# ── Wizard step handler for tarot text input ────────────────────────


async def tarot_handle_step(
    host: WizardHost,
    recipient_id: str,
    text: str,
    message_ref: object,
) -> None:
    """Handle text input during a tarot-initiated service config.

    Delegates to the inner wizard handler and returns to the spread on
    completion.
    """
    host._ensure_wizard_state()
    state = host._wizard_state.get(recipient_id)
    if not state:
        return

    # Handle "back" / "cancel" to return to spread
    if text.strip().lower() in ("back", "cancel"):
        host._wizard_state.pop(recipient_id, None)
        # Also clear instance-level tarot return context
        tarot_for = getattr(host, "_tarot_return_for", {})
        tarot_for.pop(recipient_id, None)
        await _show_tarot_spread(host, recipient_id)
        return

    inner_type = state.get("inner_type")
    if not inner_type:
        # No inner wizard active — ignore
        host._wizard_state.pop(recipient_id, None)
        return

    # Import here to avoid circular imports
    from . import _core

    handler = _core._WIZARD_HANDLERS.get(inner_type)
    if handler is None:
        host._wizard_state.pop(recipient_id, None)
        return

    # Run the inner handler
    await handler(host, recipient_id, text, message_ref)

    # Check if inner wizard cleared state (completion)
    new_state = host._wizard_state.get(recipient_id)
    if new_state is None or new_state.get("type") != "tarot_return":
        # Inner handler finished — return to spread
        await _show_tarot_spread(host, recipient_id)


async def _show_tarot_spread(host: WizardHost, recipient_id: str) -> None:
    """Re-display the tarot spread with current service status."""
    host._ensure_wizard_state()
    service_status = host._get_service_status()
    spread = get_tarot_spread(service_status)

    # Load creature for species personality
    _name, species, _owner = _load_creature_info()

    text, options = format_spread_message(spread, species, host.format_mode)

    if options:
        host._wizard_state[recipient_id] = {"type": "tarot_spread", "spread": spread}
        await host.wizard_send_menu(recipient_id, text, options)
    else:
        # All configured — send farewell and clear state
        host._wizard_state.pop(recipient_id, None)
        await host.wizard_send(recipient_id, text)


# ── Creature info loader (cached) ─────────────────────────────────

_creature_cache: tuple[float, tuple[str, str, str]] = (0.0, ("Familiar", "fox", "friend"))
_CREATURE_CACHE_TTL = 30.0  # seconds


def _load_creature_info() -> tuple[str, str, str]:
    """Load creature name, species, and owner_name from disk.

    Returns (name, species, owner_name) with safe fallbacks.
    Results are cached for 30 seconds to avoid redundant disk I/O.
    """
    global _creature_cache
    now = time.monotonic()
    if now - _creature_cache[0] < _CREATURE_CACHE_TTL:
        return _creature_cache[1]

    result = ("Familiar", "fox", "friend")
    try:
        from ...creature.state import load_creature

        creature = load_creature()
        if creature:
            name = creature.name or "Familiar"
            species = creature.species or "fox"
            owner = creature.owner_name or "friend"
            result = (name, species, owner)
    except Exception as exc:
        logger.debug("Could not load creature info: %s", exc)

    _creature_cache = (now, result)
    return result


# ── Species-flavored welcome voices ──────────────────────────────

SPECIES_WELCOME: dict[str, dict[str, str]] = {
    "fox": {
        "welcome": "Hey {owner}! I'm {name}, your fox familiar! Self-hosted, private, all yours.",
        "orientation": (
            "I can chat, connect services, give daily briefings, remember things, "
            "and learn new skills. Let's check your setup!"
        ),
        "tutorial_intro": "Let me walk you through everything again!",
    },
    "owl": {
        "welcome": "Greetings, {owner}. I am {name}, your owl familiar. Patient, precise, and entirely yours.",
        "orientation": (
            "I offer conversation, service integrations, scheduled briefings, "
            "memory, and extensible skills. Let us review your configuration."
        ),
        "tutorial_intro": "Allow me to review the essentials once more.",
    },
    "cat": {
        "welcome": "Oh, you're here. I'm {name}. Your cat familiar. Yes, I belong to you — don't let it go to your head, {owner}.",
        "orientation": (
            "I chat, connect things, schedule briefings, remember stuff, "
            "and learn skills. Shall we see what's set up? Fine."
        ),
        "tutorial_intro": "I suppose I can explain this again. Pay attention this time.",
    },
    "dragon": {
        "welcome": "Hail, {owner}! I am {name}, your dragon familiar! Forged in fire, bound to your command!",
        "orientation": (
            "I converse, conquer services, deliver briefings, guard memories, "
            "and master new skills. Let us survey your domain!"
        ),
        "tutorial_intro": "Once more into the flames — let me show you the full arsenal!",
    },
    "wolf": {
        "welcome": "Hey {owner}. I'm {name}, your wolf familiar. Part of your pack now — loyal and ready.",
        "orientation": (
            "I can talk, connect services, run briefings, track memories, "
            "and pick up new skills. Let's see what the pack needs."
        ),
        "tutorial_intro": "Let's run through the territory again — here's the full overview.",
    },
    "frog": {
        "welcome": "RIBBIT!! Hi {owner}! I'm {name}, your frog familiar! SO EXCITED to be here!!",
        "orientation": (
            "I can chat, connect stuff, do briefings, remember EVERYTHING, "
            "and learn cool new skills! Let's hop through your setup!"
        ),
        "tutorial_intro": "AGAIN AGAIN!! Let me hop through the whole thing one more time!",
    },
}

_DEFAULT_WELCOME: dict[str, str] = {
    "welcome": "Hello {owner}! I'm {name}, your AI familiar. Self-hosted, private, all yours.",
    "orientation": (
        "I can chat, connect services, give daily briefings, remember things, "
        "and learn new skills. Let's check your setup!"
    ),
    "tutorial_intro": "Let me walk you through everything again!",
}


WelcomeStep = Literal["welcome", "orientation", "tutorial_intro"]


def format_welcome_message(format_mode: FormatMode, step: WelcomeStep) -> str:
    """Return a species-voiced welcome string for *step*.

    Substitutes {owner} and {name} from creature.json, wraps in italic.
    Values are HTML-escaped when *format_mode* is HTML.
    """
    name, species, owner = _load_creature_info()
    if format_mode is FormatMode.HTML:
        name = _html_escape(name)
        owner = _html_escape(owner)
    voices = SPECIES_WELCOME.get(species, _DEFAULT_WELCOME)
    template = voices.get(step, _DEFAULT_WELCOME.get(step, ""))
    text = template.format(owner=owner, name=name)
    return fmt_italic(text, format_mode)
