# 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

"""Security wizards — Vaultwarden, Encryption, PHI Detection, RBAC, Users."""

from __future__ import annotations

import json
import logging
import os
from pathlib import Path

from ..formatting import fmt_bold, fmt_code, fmt_italic
from ._helpers import _service_doc_blurb, _test_http_service, _update_env_file
from ._protocol import WizardHost

logger = logging.getLogger(__name__)


# ── Vaultwarden ─────────────────────────────────────────────────────


async def wizard_vaultwarden(host: WizardHost, recipient_id: str, args: list) -> None:
    m = host.format_mode
    sub = args[0] if args else ""

    if sub == "test":
        await wizard_vaultwarden_test(host, recipient_id)
        return

    # /connect vaultwarden <URL> <TOKEN>
    if sub.startswith("http") and len(args) >= 2:
        url = args[0]
        token = args[1]
        await wizard_vaultwarden_save(host, recipient_id, url, token)
        return

    has_url = os.environ.get("VAULTWARDEN_URL", "")
    has_token = bool(os.environ.get("VAULTWARDEN_TOKEN"))

    if has_url and has_token:
        await host.wizard_send(
            recipient_id,
            f"\U0001f511 {fmt_bold('Vaultwarden Configuration', m)}\n\n"
            f"  \u2705 URL: {fmt_code(has_url, m)}\n"
            f"  \u2705 Token: \u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\n\n"
            f"{fmt_bold('Commands:', m)}\n"
            f"  {fmt_code('/connect vaultwarden test', m)}"
            f" \u2014 test connection\n",
        )
        return

    blurb = _service_doc_blurb("vaultwarden", m)
    if host.supports_buttons:
        await start_vaultwarden_wizard(host, recipient_id)
    else:
        await host.wizard_send(
            recipient_id,
            f"\U0001f511 {fmt_bold('Connect Vaultwarden', m)}\n"
            f"{blurb}\n"
            f"Vaultwarden is a self-hosted Bitwarden-compatible "
            f"password manager.\n\n"
            f"{fmt_bold('Setup:', m)}\n"
            f"1. Get your Vaultwarden instance URL\n"
            f"2. Generate an API token in Admin panel"
            f" \u2192 General Settings\n\n"
            f"{fmt_bold('One-shot setup:', m)}\n"
            f"  {fmt_code('/connect vaultwarden <URL> <TOKEN>', m)}"
            f"\n\n"
            f"{fmt_bold('Example:', m)}\n"
            f"  {fmt_code('/connect vaultwarden https://vault.example.org my-api-token', m)}"
            f"\n\n"
            f"{fmt_italic('Credentials saved to ~/.familiar/.env (chmod 600)', m)}",
        )


async def start_vaultwarden_wizard(host: WizardHost, recipient_id: str) -> None:
    host._ensure_wizard_state()
    m = host.format_mode
    host._wizard_state[recipient_id] = {
        "type": "vaultwarden",
        "step": "url",
    }
    blurb = _service_doc_blurb("vaultwarden", m)
    await host.wizard_send(
        recipient_id,
        f"\U0001f511 {fmt_bold('Vaultwarden Setup', m)}\n"
        f"{blurb}\n"
        f"Vaultwarden is a self-hosted Bitwarden-compatible "
        f"password manager.\n\n"
        f"Enter your {fmt_bold('Vaultwarden instance URL', m)}:\n\n"
        f"{fmt_italic('Example: https://vault.example.org', m)}",
    )


async def vaultwarden_handle_step(
    host: WizardHost,
    recipient_id: str,
    text: str,
    message_ref: object,
) -> None:
    host._ensure_wizard_state()
    m = host.format_mode
    state = host._wizard_state.get(recipient_id)
    if not state:
        return
    step = state["step"]
    text = text.strip()

    if step == "url":
        if not text.startswith(("http://", "https://")):
            await host.wizard_send(
                recipient_id,
                "That doesn't look like a URL. "
                "Please enter a URL starting with http:// or https://:",
            )
            return
        state["url"] = text.rstrip("/")
        state["step"] = "token"

        warning = ""
        if not host.supports_message_deletion:
            pw_warning = (
                "This channel cannot delete messages. "
                "Your token will remain visible in chat history. "
                "Consider using one-shot: "
                "/connect vaultwarden <URL> <TOKEN>"
            )
            warning = f"\n\n\u26a0\ufe0f {fmt_italic(pw_warning, m)}"

        await host.wizard_send(
            recipient_id,
            f"\U0001f510 Enter your "
            f"{fmt_bold('Vaultwarden API token', m)}:\n\n"
            f"Find it at: Admin panel \u2192 General Settings"
            f"{warning}"
            + (
                "\n\n"
                + fmt_italic(
                    "Your message will be deleted immediately for safety.",
                    m,
                )
                if host.supports_message_deletion
                else ""
            ),
        )

    elif step == "token":
        if host.supports_message_deletion and message_ref is not None:
            try:
                await host.wizard_delete_message(recipient_id, message_ref)
            except Exception:
                pass

        url = state["url"]
        host._wizard_state.pop(recipient_id, None)
        await host.wizard_send(recipient_id, "Testing Vaultwarden connection...")
        await wizard_vaultwarden_save(host, recipient_id, url, text)


async def wizard_vaultwarden_save(
    host: WizardHost, recipient_id: str, url: str, token: str
) -> None:
    url = url.rstrip("/")
    env_vars = {"VAULTWARDEN_URL": url, "VAULTWARDEN_TOKEN": token}
    for k, v in env_vars.items():
        os.environ[k] = v
    env_path = Path.home() / ".familiar" / ".env"
    try:
        _update_env_file(env_path, env_vars)
    except Exception as e:
        await host.wizard_send(recipient_id, f"\u26a0\ufe0f Failed to save: {e}")
        return

    await host.wizard_send(recipient_id, "Testing Vaultwarden connection...")
    await wizard_vaultwarden_test(host, recipient_id)


async def wizard_vaultwarden_test(host: WizardHost, recipient_id: str) -> None:
    m = host.format_mode
    url = os.environ.get("VAULTWARDEN_URL", "")
    token = os.environ.get("VAULTWARDEN_TOKEN", "")
    if not url or not token:
        await host.wizard_send(
            recipient_id,
            f"\u274c No Vaultwarden credentials configured.\n"
            f"Run: {fmt_code('/connect vaultwarden', m)}",
        )
        return
    ok, msg = _test_http_service(
        f"{url}/api/folders",
        headers={"Authorization": f"Bearer {token}"},
    )
    if ok:
        await host.wizard_send(
            recipient_id,
            f"\u2705 {fmt_bold('Vaultwarden connected!', m)}\n\n"
            f"  URL: {fmt_code(url, m)}\n"
            f"  Status: {msg}\n\n"
            f"Saved to: {fmt_code('~/.familiar/.env', m)}",
        )
    else:
        await host.wizard_send(
            recipient_id,
            f"\u274c {fmt_bold('Vaultwarden connection failed', m)}\n\n"
            f"  {msg}\n\n"
            f"Check your URL and API token, then try again.",
        )


# ── Encryption ──────────────────────────────────────────────────────


async def wizard_encryption(host: WizardHost, recipient_id: str) -> None:
    m = host.format_mode
    ck = "\u2705"
    em = "\u2b1c"
    data_dir = Path.home() / ".familiar" / "data"
    key_dir = Path.home() / ".familiar" / "keys"

    # Check cryptography library
    has_crypto = False
    try:
        import cryptography  # noqa: F401

        has_crypto = True
    except ImportError:
        pass

    # Key status
    key_files = list(key_dir.glob("*.key")) if key_dir.exists() else []
    key_count = len(key_files)

    # Encrypted stores
    encrypted_count = 0
    total_stores = 0
    if data_dir.exists():
        for f in data_dir.glob("*.json"):
            total_stores += 1
            enc_path = f.with_suffix(".json.enc")
            if enc_path.exists():
                encrypted_count += 1

    _icon = ck if has_crypto else em
    _status = "installed" if has_crypto else "not installed"
    _icon2 = ck if key_count > 0 else em
    _icon3 = ck if encrypted_count > 0 else em
    lines = [
        f"\U0001f510 {fmt_bold('Encryption (At Rest)', m)}\n",
        f"  {_icon} cryptography library: {_status}",
        f"  {_icon2} Encryption keys: {key_count} key(s) in ~/.familiar/keys/",
        f"  {_icon3} Encrypted stores: {encrypted_count}/{total_stores} JSON stores\n",
    ]

    if key_count > 0:
        _cmd = '"encryption status"'
        _cmd2 = '"encrypt all data stores"'
        _cmd3 = '"rotate encryption keys"'
        lines.append(f"{fmt_bold('Quick actions:', m)}")
        lines.append(f"  {fmt_italic(_cmd, m)} \u2014 detailed key info")
        lines.append(f"  {fmt_italic(_cmd2, m)} \u2014 encrypt unprotected files")
        lines.append(f"  {fmt_italic(_cmd3, m)} \u2014 generate new key + re-encrypt")
    else:
        lines.append(f"{fmt_bold('Getting started:', m)}")
        if not has_crypto:
            # cryptography is a core dependency — auto-install if
            # somehow missing
            from familiar.core.deps import ensure_packages

            ok, _ = ensure_packages([("cryptography", "cryptography")])
            has_crypto = ok
        if has_crypto:
            _cmd = '"generate encryption key"'
            lines.append(f"  Ask me to {fmt_italic(_cmd, m)}")
        else:
            lines.append(
                "  \u26a0\ufe0f cryptography library could not be installed — check server logs"
            )
        lines.append(f"  Keys are stored in {fmt_code('~/.familiar/keys/', m)} (chmod 600)")

    if host.supports_buttons:
        if key_count > 0:
            await host.wizard_send_menu(
                recipient_id,
                "\n".join(lines),
                [
                    (
                        "encryption_encrypt_all",
                        "\U0001f510 Encrypt all stores",
                    ),
                    (
                        "encryption_rotate",
                        "\U0001f504 Rotate keys",
                    ),
                ],
            )
        else:
            await host.wizard_send_menu(
                recipient_id,
                "\n".join(lines),
                [
                    (
                        "encryption_gen_key",
                        "\U0001f511 Generate encryption key",
                    ),
                ],
            )
    else:
        await host.wizard_send(recipient_id, "\n".join(lines))


# ── PHI Detection ───────────────────────────────────────────────────


async def wizard_phi_detection(host: WizardHost, recipient_id: str) -> None:
    m = host.format_mode
    data_dir = Path.home() / ".familiar" / "data"

    # Load PHI config
    safe_mode = False
    sensitivity = "standard"
    auto_redact = False
    log_detections = False
    whitelist_count = 0
    try:
        phi_path = data_dir / "phi_config.json"
        if phi_path.exists():
            phi = json.loads(phi_path.read_text())
            safe_mode = phi.get("safe_mode", False)
            sensitivity = phi.get("sensitivity", "standard")
            auto_redact = phi.get("auto_redact", False)
            log_detections = phi.get("log_detections", False)
            whitelist_count = len(phi.get("whitelist", []))
    except Exception:
        pass

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

    _icon = ck if safe_mode else em
    _status = "enabled" if safe_mode else "disabled"
    _icon2 = ck if auto_redact else em
    _status2 = "on" if auto_redact else "off"
    _icon3 = ck if log_detections else em
    _status3 = "on" if log_detections else "off"
    _cmd = '"phi detection status"'
    _cmd2 = '"enable safe mode"'
    _cmd3 = '"set phi sensitivity to strict"'
    _cmd4 = '"scan text for PHI"'
    _cmd5 = '"phi classification report"'
    lines = [
        f"\U0001f3e5 {fmt_bold('PHI Detection', m)}\n",
        f"{fmt_bold('Current config:', m)}",
        f"  {_icon} Safe mode: {_status}",
        f"  Sensitivity: {fmt_bold(sensitivity, m)}",
        f"  {_icon2} Auto-redact: {_status2}",
        f"  {_icon3} Log detections: {_status3}",
        f"  Whitelist entries: {whitelist_count}\n",
        f"{fmt_bold('Sensitivity levels:', m)}",
        f"  {fmt_bold('minimal', m)} \u2014 SSNs, medical record numbers only",
        f"  {fmt_bold('standard', m)} \u2014 + names, dates, addresses, phone numbers",
        f"  {fmt_bold('strict', m)} \u2014 + any potential identifier, aggressive matching\n",
        f"{fmt_bold('Quick actions:', m)}",
        f"  {fmt_italic(_cmd, m)} \u2014 current settings",
        f"  {fmt_italic(_cmd2, m)} \u2014 block PHI in responses",
        f"  {fmt_italic(_cmd3, m)} \u2014 change level",
        f"  {fmt_italic(_cmd4, m)} \u2014 test detection",
        f"  {fmt_italic(_cmd5, m)} \u2014 detection summary",
    ]

    if host.supports_buttons:
        await host.wizard_send_menu(
            recipient_id,
            "\n".join(lines),
            [
                (
                    "phi_enable_safe",
                    "\U0001f6e1\ufe0f Enable safe mode",
                ),
                (
                    "phi_set_strict",
                    "\u26a0\ufe0f Set strict sensitivity",
                ),
            ],
        )
    else:
        await host.wizard_send(recipient_id, "\n".join(lines))


# ── RBAC ────────────────────────────────────────────────────────────


async def wizard_rbac(host: WizardHost, recipient_id: str) -> None:
    m = host.format_mode
    data_dir = Path.home() / ".familiar" / "data"

    # Built-in roles
    builtin_roles = [
        ("admin", "Full access to all skills and tools"),
        ("operator", "Manage services, users, and configuration"),
        ("analyst", "Read-only access to data and reports"),
        (
            "healthcare",
            "HIPAA-scoped: PHI access + audit logging",
        ),
        ("developer", "Code, browser, and integration tools"),
        ("guest", "Minimal access: chat and basic queries"),
    ]

    # Load custom roles and assignments
    custom_roles: list[dict] = []
    assignments: dict = {}
    try:
        rbac_path = data_dir / "rbac.json"
        if rbac_path.exists():
            rbac = json.loads(rbac_path.read_text())
            custom_roles = rbac.get("custom_roles", [])
            assignments = rbac.get("assignments", {})
    except Exception:
        pass

    lines = [
        f"\U0001f6e1\ufe0f {fmt_bold('Role-Based Access Control', m)}\n",
        f"{fmt_bold('Built-in roles:', m)}",
    ]
    for role, desc in builtin_roles:
        lines.append(f"  {fmt_bold(role, m)} \u2014 {desc}")

    if custom_roles:
        lines.append(f"\n{fmt_bold('Custom roles:', m)} ({len(custom_roles)})")
        for cr in custom_roles[:5]:
            name = cr.get("name", "?")
            perms = len(cr.get("permissions", []))
            lines.append(f"  {fmt_bold(name, m)} \u2014 {perms} permission(s)")
        if len(custom_roles) > 5:
            lines.append(f"  ... and {len(custom_roles) - 5} more")

    assign_count = len(assignments)
    lines.append(f"\n{fmt_bold('Assignments:', m)} {assign_count} user(s) have role mappings")
    if assignments:
        for user_id, role in list(assignments.items())[:5]:
            lines.append(f"  {user_id} \u2192 {role}")
        if assign_count > 5:
            lines.append(f"  ... and {assign_count - 5} more")

    lines.append(
        f"\n{fmt_bold('Permission format:', m)}"
        f" {fmt_code('skill:tool', m)}"
        f" (e.g. {fmt_code('email:send_email', m)})"
    )

    _cmd = '"list roles"'
    _cmd2 = '"create role analyst-plus with email:read,calendar:read"'
    _cmd3 = '"assign role developer to user123"'
    _cmd4 = '"check permission email:send for user123"'
    lines.append(f"\n{fmt_bold('Quick actions:', m)}")
    lines.append(f"  {fmt_italic(_cmd, m)} \u2014 show all roles and permissions")
    lines.append(f"  {fmt_italic(_cmd2, m)}")
    lines.append(f"  {fmt_italic(_cmd3, m)}")
    lines.append(f"  {fmt_italic(_cmd4, m)}")

    if host.supports_buttons:
        await host.wizard_send_menu(
            recipient_id,
            "\n".join(lines),
            [
                ("rbac_list", "\U0001f4cb List all roles"),
                ("rbac_create", "\u2795 Create custom role"),
                (
                    "rbac_assign",
                    "\U0001f464 Assign role to user",
                ),
            ],
        )
    else:
        await host.wizard_send(recipient_id, "\n".join(lines))


# ── User Management ─────────────────────────────────────────────────


async def wizard_user_management(host: WizardHost, recipient_id: str) -> None:
    m = host.format_mode
    data_dir = Path.home() / ".familiar" / "data"
    ck = "\u2705"
    cr = "\u274c"

    # Load users
    users: list[dict] = []
    pre_auths: list = []
    try:
        users_path = data_dir / "users.json"
        if users_path.exists():
            data = json.loads(users_path.read_text())
            users = data.get("users", [])
            pre_auths = data.get("pre_authorizations", [])
    except Exception:
        pass

    active_count = sum(1 for u in users if u.get("active", True))
    deactivated_count = len(users) - active_count

    lines = [
        f"\U0001f465 {fmt_bold('User Management', m)}\n",
        f"  Users: {len(users)} total ({active_count} active",
    ]
    if deactivated_count:
        lines[-1] += f", {deactivated_count} deactivated)"
    else:
        lines[-1] += ")"
    lines.append(f"  Pre-authorizations: {len(pre_auths)}")

    if users:
        lines.append(f"\n{fmt_bold('Users:', m)}")
        for u in users[:8]:
            status = ck if u.get("active", True) else cr
            name = u.get("name", u.get("username", "?"))
            email = u.get("email", "")
            role = u.get("role", "guest")
            line = f"  {status} {name}"
            if email:
                line += f" ({email})"
            line += f" \u2014 {role}"
            lines.append(line)
        if len(users) > 8:
            lines.append(f"  ... and {len(users) - 8} more")

    lines.append(f"\n{fmt_bold('Auth:', m)} PBKDF2-SHA256 password hashing")

    _cmd = '"list users"'
    _cmd2 = '"create user alice alice@example.com"'
    _cmd3 = '"deactivate user bob"'
    _cmd4 = '"list pre-authorizations"'
    _cmd5 = '"grant preauth for alice@example.com"'
    lines.append(f"\n{fmt_bold('Quick actions:', m)}")
    lines.append(f"  {fmt_italic(_cmd, m)} \u2014 show all accounts")
    lines.append(f"  {fmt_italic(_cmd2, m)}")
    lines.append(f"  {fmt_italic(_cmd3, m)}")
    lines.append(f"  {fmt_italic(_cmd4, m)}")
    lines.append(f"  {fmt_italic(_cmd5, m)}")

    if not users:
        _cmd6 = '"create a user account"'
        lines.append(f"\n{fmt_bold('Getting started:', m)}")
        lines.append(f"  Ask me to {fmt_italic(_cmd6, m)} to begin.")
        lines.append(f"  User data stored in {fmt_code('~/.familiar/data/users.json', m)}")

    if host.supports_buttons:
        await host.wizard_send_menu(
            recipient_id,
            "\n".join(lines),
            [
                ("users_list", "\U0001f4cb List users"),
                ("users_create", "\u2795 Create new user"),
            ],
        )
    else:
        await host.wizard_send(recipient_id, "\n".join(lines))


# ── Menu selection dispatcher ───────────────────────────────────────


async def handle_menu_selection(host: WizardHost, recipient_id: str, key: str) -> bool:
    """Handle a security-related menu button press.

    Returns ``True`` if *key* was recognised and handled.
    """
    m = host.format_mode

    # Top-level security wizard menus
    if key == "vaultwarden_menu":
        await wizard_vaultwarden(host, recipient_id, [])
        return True
    if key == "encryption_menu":
        await wizard_encryption(host, recipient_id)
        return True
    if key == "phi_detection_menu":
        await wizard_phi_detection(host, recipient_id)
        return True
    if key == "rbac_menu":
        await wizard_rbac(host, recipient_id)
        return True
    if key == "user_management_menu":
        await wizard_user_management(host, recipient_id)
        return True

    # Encryption action buttons
    if key == "encryption_gen_key":
        _cmd = '"generate encryption key"'
        await host.wizard_send(
            recipient_id,
            f"\U0001f511 To generate an encryption key, send me:\n\n  {fmt_italic(_cmd, m)}",
        )
        return True
    if key == "encryption_encrypt_all":
        _cmd = '"encrypt all data stores"'
        await host.wizard_send(
            recipient_id,
            f"\U0001f510 To encrypt all data stores, send me:\n\n  {fmt_italic(_cmd, m)}",
        )
        return True
    if key == "encryption_rotate":
        _cmd = '"rotate encryption keys"'
        await host.wizard_send(
            recipient_id,
            f"\U0001f504 To rotate encryption keys, send me:\n\n  {fmt_italic(_cmd, m)}",
        )
        return True

    # PHI Detection action buttons
    if key == "phi_enable_safe":
        _cmd = '"enable safe mode"'
        await host.wizard_send(
            recipient_id,
            f"\U0001f6e1\ufe0f To enable safe mode, send me:\n\n  {fmt_italic(_cmd, m)}",
        )
        return True
    if key == "phi_set_strict":
        _cmd = '"set phi sensitivity to strict"'
        await host.wizard_send(
            recipient_id,
            f"\u26a0\ufe0f To set strict PHI sensitivity, send me:\n\n  {fmt_italic(_cmd, m)}",
        )
        return True

    # RBAC action buttons
    if key == "rbac_list":
        _cmd = '"list roles"'
        await host.wizard_send(
            recipient_id,
            f"\U0001f4cb To list all roles, send me:\n\n  {fmt_italic(_cmd, m)}",
        )
        return True
    if key == "rbac_create":
        _cmd = '"create role <name> with <permissions>"'
        _cmd2 = '"create role analyst-plus with email:read,calendar:read"'
        await host.wizard_send(
            recipient_id,
            f"\u2795 To create a custom role, send me:\n\n"
            f"  {fmt_italic(_cmd, m)}\n\n"
            f"Example: {fmt_italic(_cmd2, m)}",
        )
        return True
    if key == "rbac_assign":
        _cmd = '"assign role <role> to <user>"'
        _cmd2 = '"assign role developer to user123"'
        await host.wizard_send(
            recipient_id,
            f"\U0001f464 To assign a role, send me:\n\n"
            f"  {fmt_italic(_cmd, m)}\n\n"
            f"Example: {fmt_italic(_cmd2, m)}",
        )
        return True

    # User Management action buttons
    if key == "users_list":
        _cmd = '"list users"'
        await host.wizard_send(
            recipient_id,
            f"\U0001f4cb To list all users, send me:\n\n  {fmt_italic(_cmd, m)}",
        )
        return True
    if key == "users_create":
        _cmd = '"create user <name> <email>"'
        _cmd2 = '"create user alice alice@example.com"'
        await host.wizard_send(
            recipient_id,
            f"\u2795 To create a new user, send me:\n\n"
            f"  {fmt_italic(_cmd, m)}\n\n"
            f"Example: {fmt_italic(_cmd2, m)}",
        )
        return True

    return False
