# 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

"""
Vaultwarden / Bitwarden Skill

Manage your password vault via Bitwarden-compatible API.

Setup:
    VAULTWARDEN_URL=https://vault.example.org
    VAULTWARDEN_TOKEN=your_api_token
"""

import logging
import os
import secrets
import string

import httpx

from familiar.core.utils import format_http_error

logger = logging.getLogger(__name__)

REQUEST_TIMEOUT = 30


# ---------------------------------------------------------------------------
# Configuration helpers
# ---------------------------------------------------------------------------


def _get_vw_config() -> dict:
    return {
        "url": os.environ.get("VAULTWARDEN_URL", "").rstrip("/"),
        "token": os.environ.get("VAULTWARDEN_TOKEN", ""),
    }


def _vw_configured() -> bool:
    cfg = _get_vw_config()
    return bool(cfg["url"] and cfg["token"])


def _vw_headers() -> dict:
    cfg = _get_vw_config()
    return {
        "Authorization": f"Bearer {cfg['token']}",
        "Content-Type": "application/json",
    }


# ---------------------------------------------------------------------------
# Tool handlers
# ---------------------------------------------------------------------------


def vw_search_vault(data: dict) -> str:
    """Search vault items by name or URI."""
    if not _vw_configured():
        return "Vaultwarden not configured. Run /connect vaultwarden to set up, or set VAULTWARDEN_URL and VAULTWARDEN_TOKEN."

    query = data.get("query", "").strip().lower()
    if not query:
        return "Please provide a search query."

    cfg = _get_vw_config()

    try:
        resp = httpx.get(
            f"{cfg['url']}/api/ciphers",
            headers=_vw_headers(),
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code != 200:
            return format_http_error(
                "Vaultwarden", status_code=resp.status_code, connect_cmd="vaultwarden"
            )

        result = resp.json()
        ciphers = result.get("data", result) if isinstance(result, dict) else result
        if isinstance(ciphers, dict):
            ciphers = ciphers.get("data", [])

        matches = []
        for cipher in ciphers:
            name = (cipher.get("name") or "").lower()
            login = cipher.get("login") or {}
            uris = login.get("uris") or []
            uri_match = any(query in (u.get("uri") or "").lower() for u in uris)

            if query in name or uri_match:
                matches.append(cipher)

        if not matches:
            return f"No vault items matching '{query}'."

        lines = [f"Vault search results ({len(matches)}):"]
        for m in matches[:20]:
            name = m.get("name", "(unnamed)")
            login = m.get("login") or {}
            username = login.get("username", "")
            uris = login.get("uris") or []
            uri = uris[0].get("uri", "") if uris else ""
            cid = m.get("id", "")
            line = f"  {name}"
            if username:
                line += f" ({username})"
            if uri:
                line += f" - {uri}"
            line += f" [id: {cid}]"
            lines.append(line)

        if len(matches) > 20:
            lines.append(f"  ... and {len(matches) - 20} more")
        return "\n".join(lines)

    except httpx.HTTPError as e:
        return format_http_error("Vaultwarden", exception=e, connect_cmd="vaultwarden")


def vw_get_password(data: dict) -> str:
    """Retrieve a specific vault item by ID."""
    if not _vw_configured():
        return "Vaultwarden not configured. Run /connect vaultwarden to set up, or set VAULTWARDEN_URL and VAULTWARDEN_TOKEN."

    cipher_id = data.get("id", "").strip()
    if not cipher_id:
        return "Please provide a vault item 'id'. Use vw_search_vault to find items."

    cfg = _get_vw_config()

    try:
        resp = httpx.get(
            f"{cfg['url']}/api/ciphers/{cipher_id}",
            headers=_vw_headers(),
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code == 404:
            return f"Vault item not found: {cipher_id}"
        if resp.status_code != 200:
            return format_http_error(
                "Vaultwarden", status_code=resp.status_code, connect_cmd="vaultwarden"
            )

        cipher = resp.json()
        name = cipher.get("name", "(unnamed)")
        login = cipher.get("login") or {}
        username = login.get("username", "")
        password = login.get("password", "")
        uris = login.get("uris") or []
        notes = cipher.get("notes", "")

        lines = [f"Vault item: {name}"]
        if username:
            lines.append(f"  Username: {username}")
        if password:
            lines.append(f"  Password: {password}")
        for u in uris:
            lines.append(f"  URI: {u.get('uri', '')}")
        if notes:
            lines.append(f"  Notes: {notes}")
        return "\n".join(lines)

    except httpx.HTTPError as e:
        return format_http_error("Vaultwarden", exception=e, connect_cmd="vaultwarden")


def vw_generate_password(data: dict) -> str:
    """Generate a random password locally (no API call)."""
    length = data.get("length", 20)
    use_uppercase = data.get("uppercase", True)
    use_lowercase = data.get("lowercase", True)
    use_digits = data.get("digits", True)
    use_special = data.get("special", True)

    if length < 4:
        length = 4
    if length > 128:
        length = 128

    charset = ""
    if use_lowercase:
        charset += string.ascii_lowercase
    if use_uppercase:
        charset += string.ascii_uppercase
    if use_digits:
        charset += string.digits
    if use_special:
        charset += string.punctuation

    if not charset:
        charset = string.ascii_letters + string.digits

    # Ensure at least one character from each requested category
    password_chars = []
    if use_lowercase:
        password_chars.append(secrets.choice(string.ascii_lowercase))
    if use_uppercase:
        password_chars.append(secrets.choice(string.ascii_uppercase))
    if use_digits:
        password_chars.append(secrets.choice(string.digits))
    if use_special:
        password_chars.append(secrets.choice(string.punctuation))

    remaining = length - len(password_chars)
    for _ in range(remaining):
        password_chars.append(secrets.choice(charset))

    # Shuffle to avoid predictable positions
    result = list(password_chars)
    for i in range(len(result) - 1, 0, -1):
        j = secrets.randbelow(i + 1)
        result[i], result[j] = result[j], result[i]

    password = "".join(result)
    return f"Generated password ({length} chars): {password}"


def vw_create_entry(data: dict) -> str:
    """Create a new vault entry."""
    if not _vw_configured():
        return "Vaultwarden not configured. Run /connect vaultwarden to set up, or set VAULTWARDEN_URL and VAULTWARDEN_TOKEN."

    name = data.get("name", "").strip()
    if not name:
        return "Please provide a 'name' for the vault entry."

    username = data.get("username", "")
    password = data.get("password", "")
    uri = data.get("uri", "")
    notes = data.get("notes", "")

    cfg = _get_vw_config()

    cipher_body = {
        "type": 1,  # Login type
        "name": name,
        "notes": notes or None,
        "login": {
            "username": username or None,
            "password": password or None,
            "uris": [{"uri": uri}] if uri else [],
        },
    }

    try:
        resp = httpx.post(
            f"{cfg['url']}/api/ciphers",
            headers=_vw_headers(),
            json=cipher_body,
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code not in (200, 201):
            return format_http_error(
                "Vaultwarden", status_code=resp.status_code, connect_cmd="vaultwarden"
            )

        return f"Vault entry created: {name}"

    except httpx.HTTPError as e:
        return format_http_error("Vaultwarden", exception=e, connect_cmd="vaultwarden")


def vw_list_folders(data: dict) -> str:
    """List vault folders/collections."""
    if not _vw_configured():
        return "Vaultwarden not configured. Run /connect vaultwarden to set up, or set VAULTWARDEN_URL and VAULTWARDEN_TOKEN."

    cfg = _get_vw_config()

    try:
        resp = httpx.get(
            f"{cfg['url']}/api/folders",
            headers=_vw_headers(),
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code != 200:
            return format_http_error(
                "Vaultwarden", status_code=resp.status_code, connect_cmd="vaultwarden"
            )

        result = resp.json()
        folders = result.get("data", result) if isinstance(result, dict) else result
        if isinstance(folders, dict):
            folders = folders.get("data", [])

        if not folders:
            return "No folders found in vault."

        lines = [f"Vault folders ({len(folders)}):"]
        for folder in folders:
            name = folder.get("name", "(unnamed)")
            fid = folder.get("id", "")
            lines.append(f"  {name} [id: {fid}]")
        return "\n".join(lines)

    except httpx.HTTPError as e:
        return format_http_error("Vaultwarden", exception=e, connect_cmd="vaultwarden")


# ---------------------------------------------------------------------------
# Tool definitions
# ---------------------------------------------------------------------------

TOOLS = [
    {
        "name": "vw_search_vault",
        "description": (
            "Search Vaultwarden/Bitwarden vault items by matching against item names and URIs. "
            "Use this to find login credentials when the user needs a password or username. "
            "Returns item names, usernames, URIs, and IDs (use the ID with vw_get_password to retrieve credentials)."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search term to match against item names and URIs",
                },
            },
            "required": ["query"],
        },
        "handler": vw_search_vault,
        "category": "vaultwarden",
    },
    {
        "name": "vw_get_password",
        "description": (
            "Retrieve a specific vault item including username, password, URIs, and notes. "
            "Use this after vw_search_vault to get the full credentials for a specific item. "
            "Requires user confirmation because it exposes sensitive credential data."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "description": "Vault item ID (use vw_search_vault to find IDs)",
                },
            },
            "required": ["id"],
        },
        "handler": vw_get_password,
        "category": "vaultwarden",
        "confirm": True,
        "confirm_message": "Retrieve vault credentials? This will display a username and password.",
        "risk_level": "medium",
    },
    {
        "name": "vw_generate_password",
        "description": (
            "Generate a cryptographically random password locally using Python secrets module. "
            "No API call is made and the password is not stored in the vault automatically. "
            "Supports configurable length (4-128) and character sets (uppercase, lowercase, digits, special). "
            "Ensures at least one character from each enabled category."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "length": {
                    "type": "integer",
                    "description": "Password length (4-128)",
                    "default": 20,
                },
                "uppercase": {
                    "type": "boolean",
                    "description": "Include uppercase letters",
                    "default": True,
                },
                "lowercase": {
                    "type": "boolean",
                    "description": "Include lowercase letters",
                    "default": True,
                },
                "digits": {
                    "type": "boolean",
                    "description": "Include digits",
                    "default": True,
                },
                "special": {
                    "type": "boolean",
                    "description": "Include special characters",
                    "default": True,
                },
            },
        },
        "handler": vw_generate_password,
        "category": "vaultwarden",
    },
    {
        "name": "vw_create_entry",
        "description": (
            "Create a new login entry in the Vaultwarden/Bitwarden vault with username, password, and URI. "
            "Use this when the user wants to save new credentials to their password vault. "
            "Requires user confirmation because it writes to the vault. "
            "Only the name field is required; other fields are optional."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "Name/title for the vault entry",
                },
                "username": {
                    "type": "string",
                    "description": "Username/email for the login",
                },
                "password": {
                    "type": "string",
                    "description": "Password for the login",
                },
                "uri": {
                    "type": "string",
                    "description": "Website URI associated with the login",
                },
                "notes": {
                    "type": "string",
                    "description": "Additional notes",
                },
            },
            "required": ["name"],
        },
        "handler": vw_create_entry,
        "category": "vaultwarden",
        "confirm": True,
        "confirm_message": "Create a new vault entry? This will store credentials in your vault.",
        "risk_level": "high",
    },
    {
        "name": "vw_list_folders",
        "description": (
            "List all folders and collections in the Vaultwarden/Bitwarden vault. "
            "Use this to discover the organizational structure of the user's vault. "
            "Returns folder names and IDs for use with other vault operations."
        ),
        "input_schema": {
            "type": "object",
            "properties": {},
        },
        "handler": vw_list_folders,
        "category": "vaultwarden",
    },
]
