# 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

"""
Joplin Skill

Manage notes and notebooks via the Joplin Data API.

Setup:
    JOPLIN_TOKEN=your_joplin_api_token
    JOPLIN_URL=http://localhost:41184  (optional, this is the default)
"""

import logging
import os

import httpx

from familiar.core.utils import format_http_error

logger = logging.getLogger(__name__)

REQUEST_TIMEOUT = 30


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


def _get_joplin_config() -> dict:
    return {
        "url": os.environ.get("JOPLIN_URL", "http://localhost:41184").rstrip("/"),
        "token": os.environ.get("JOPLIN_TOKEN", ""),
    }


def _joplin_configured() -> bool:
    cfg = _get_joplin_config()
    return bool(cfg["token"])


def _joplin_url(path: str) -> str:
    """Build a full Joplin API URL with token as query parameter."""
    cfg = _get_joplin_config()
    separator = "&" if "?" in path else "?"
    return f"{cfg['url']}{path}{separator}token={cfg['token']}"


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


def joplin_list_notebooks(data: dict) -> str:
    """List all notebooks."""
    if not _joplin_configured():
        return "Joplin not configured. Run /connect joplin to set up, or set JOPLIN_TOKEN environment variable."

    try:
        resp = httpx.get(
            _joplin_url("/folders"),
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code != 200:
            return format_http_error("Joplin", status_code=resp.status_code, connect_cmd="joplin")

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

        if not notebooks:
            return "No notebooks found."

        lines = [f"Notebooks ({len(notebooks)}):"]
        for nb in notebooks:
            title = nb.get("title", "(untitled)")
            nid = nb.get("id", "")
            parent = nb.get("parent_id", "")
            indent = "    " if parent else "  "
            lines.append(f"{indent}{title} [id: {nid}]")
        return "\n".join(lines)

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


def joplin_search_notes(data: dict) -> str:
    """Search notes by query string."""
    if not _joplin_configured():
        return "Joplin not configured. Run /connect joplin to set up, or set JOPLIN_TOKEN environment variable."

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

    try:
        resp = httpx.get(
            _joplin_url(f"/search?query={query}&type=note"),
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code != 200:
            return format_http_error("Joplin", status_code=resp.status_code, connect_cmd="joplin")

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

        if not notes:
            return f"No notes matching '{query}'."

        lines = [f"Search results for '{query}' ({len(notes)}):"]
        for note in notes[:20]:
            title = note.get("title", "(untitled)")
            nid = note.get("id", "")
            updated = note.get("updated_time", "")
            if isinstance(updated, (int, float)):
                from datetime import datetime

                try:
                    updated = datetime.fromtimestamp(updated / 1000).strftime("%Y-%m-%d %H:%M")
                except (OSError, ValueError):
                    updated = ""
            lines.append(f"  {title} [id: {nid}]")
            if updated:
                lines.append(f"    Updated: {updated}")

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

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


def joplin_get_note(data: dict) -> str:
    """Get a note's full content by ID."""
    if not _joplin_configured():
        return "Joplin not configured. Run /connect joplin to set up, or set JOPLIN_TOKEN environment variable."

    note_id = data.get("id", "").strip()
    if not note_id:
        return "Please provide a note 'id'. Use joplin_search_notes to find notes."

    try:
        resp = httpx.get(
            _joplin_url(f"/notes/{note_id}?fields=id,title,body"),
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code == 404:
            return f"Note not found: {note_id}"
        if resp.status_code != 200:
            return format_http_error("Joplin", status_code=resp.status_code, connect_cmd="joplin")

        note = resp.json()
        title = note.get("title", "(untitled)")
        body = note.get("body", "")

        lines = [f"# {title}", ""]
        if body:
            # Truncate very long notes
            if len(body) > 5000:
                lines.append(body[:5000])
                lines.append(f"\n... (truncated, {len(body)} chars total)")
            else:
                lines.append(body)
        else:
            lines.append("(empty note)")
        return "\n".join(lines)

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


def joplin_create_note(data: dict) -> str:
    """Create a new note."""
    if not _joplin_configured():
        return "Joplin not configured. Run /connect joplin to set up, or set JOPLIN_TOKEN environment variable."

    title = data.get("title", "").strip()
    if not title:
        return "Please provide a 'title' for the note."

    body = data.get("body", "")
    parent_id = data.get("parent_id", "")

    note_body = {
        "title": title,
        "body": body,
    }
    if parent_id:
        note_body["parent_id"] = parent_id

    try:
        resp = httpx.post(
            _joplin_url("/notes"),
            json=note_body,
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code not in (200, 201):
            return format_http_error("Joplin", status_code=resp.status_code, connect_cmd="joplin")

        result = resp.json()
        note_id = result.get("id", "")
        return f"Note created: {title} [id: {note_id}]"

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


def joplin_update_note(data: dict) -> str:
    """Update an existing note's title and/or body."""
    if not _joplin_configured():
        return "Joplin not configured. Run /connect joplin to set up, or set JOPLIN_TOKEN environment variable."

    note_id = data.get("id", "").strip()
    if not note_id:
        return "Please provide a note 'id'. Use joplin_search_notes to find notes."

    title = data.get("title", "")
    body = data.get("body", "")

    if not title and not body:
        return "Please provide a 'title' and/or 'body' to update."

    update_body = {}
    if title:
        update_body["title"] = title
    if body:
        update_body["body"] = body

    try:
        resp = httpx.put(
            _joplin_url(f"/notes/{note_id}"),
            json=update_body,
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code == 404:
            return f"Note not found: {note_id}"
        if resp.status_code != 200:
            return format_http_error("Joplin", status_code=resp.status_code, connect_cmd="joplin")

        return f"Note updated: {title or note_id}"

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


def joplin_list_tags(data: dict) -> str:
    """List all tags."""
    if not _joplin_configured():
        return "Joplin not configured. Run /connect joplin to set up, or set JOPLIN_TOKEN environment variable."

    try:
        resp = httpx.get(
            _joplin_url("/tags"),
            timeout=REQUEST_TIMEOUT,
        )
        if resp.status_code != 200:
            return format_http_error("Joplin", status_code=resp.status_code, connect_cmd="joplin")

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

        if not tags:
            return "No tags found."

        lines = [f"Tags ({len(tags)}):"]
        for tag in tags:
            title = tag.get("title", "(unnamed)")
            tid = tag.get("id", "")
            lines.append(f"  {title} [id: {tid}]")
        return "\n".join(lines)

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


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

TOOLS = [
    {
        "name": "joplin_list_notebooks",
        "description": (
            "List all notebooks in the user's Joplin instance via the Data API. "
            "Use this to discover available notebooks and their IDs before creating or searching notes. "
            "Returns notebook titles and IDs, with indentation for sub-notebooks."
        ),
        "input_schema": {
            "type": "object",
            "properties": {},
        },
        "handler": joplin_list_notebooks,
        "category": "joplin",
    },
    {
        "name": "joplin_search_notes",
        "description": (
            "Search Joplin notes by query string against note titles and content. "
            "This searches the user's personal notes app, NOT agent memory or knowledge base. "
            "For agent memory use recall; for ingested docs use search_knowledge. "
            "Returns up to 20 matching notes with titles, IDs, and last-updated timestamps."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search term to match against note titles and content",
                },
            },
            "required": ["query"],
        },
        "handler": joplin_search_notes,
        "category": "joplin",
    },
    {
        "name": "joplin_get_note",
        "description": "Get the full content of a Joplin note by ID",
        "input_schema": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "description": "Note ID (use joplin_search_notes to find IDs)",
                },
            },
            "required": ["id"],
        },
        "handler": joplin_get_note,
        "category": "joplin",
    },
    {
        "name": "joplin_create_note",
        "description": "Create a new note in Joplin",
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {
                    "type": "string",
                    "description": "Note title",
                },
                "body": {
                    "type": "string",
                    "description": "Note body content (Markdown supported)",
                },
                "parent_id": {
                    "type": "string",
                    "description": "Notebook ID to create the note in (optional)",
                },
            },
            "required": ["title"],
        },
        "handler": joplin_create_note,
        "category": "joplin",
    },
    {
        "name": "joplin_update_note",
        "description": "Update an existing Joplin note's title and/or body",
        "input_schema": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "description": "Note ID to update",
                },
                "title": {
                    "type": "string",
                    "description": "New title (leave empty to keep current)",
                },
                "body": {
                    "type": "string",
                    "description": "New body content (leave empty to keep current)",
                },
            },
            "required": ["id"],
        },
        "handler": joplin_update_note,
        "category": "joplin",
    },
    {
        "name": "joplin_list_tags",
        "description": "List all tags in Joplin",
        "input_schema": {
            "type": "object",
            "properties": {},
        },
        "handler": joplin_list_tags,
        "category": "joplin",
    },
]
