# 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
from typing import TYPE_CHECKING

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! Let's make some mischief.",
        "allclear": "The stars align — your familiar is fully attuned!",
    },
    "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.",
        "allclear": "The stars align — your familiar is fully attuned!",
    },
    "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. Don't say I never did anything for you.",
        "allclear": "The stars align — your familiar is fully attuned!",
    },
    "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 complete. Onward!",
        "allclear": "The stars align — your familiar is fully attuned!",
    },
    "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. Ready to run.",
        "allclear": "The stars align — your familiar is fully attuned!",
    },
    "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! Your frog is all set up and ready to leap!",
        "allclear": "The stars align — your familiar is fully attuned!",
    },
}

_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. Your familiar is ready.",
    "allclear": "The stars align — your familiar is fully attuned!",
}


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


# ── 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)}",
            [],
        )

    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.
    """
    voice = _voice(species)
    arcana = TAROT_ARCANA.get(arcana_key)
    if not arcana:
        return ("Unknown card.", [])

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

    lines: list[str] = [
        f"{arcana['icon']} {fmt_bold(arcana['name'], format_mode)} ({arcana['numeral']})",
        "",
        f"{fmt_italic(voice['reveal'], format_mode)}",
        "",
        fmt_italic(arcana["description"], 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
    species = _get_creature_species()

    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)


def _get_creature_species() -> str:
    """Load the creature species from disk, defaulting to 'fox'."""
    try:
        from ...creature.state import load_creature

        creature = load_creature()
        if creature:
            return creature.species
    except Exception:
        pass
    return "fox"
