"""
MCP server dla protokołu Nostr.

Narzędzia: connect_relay, disconnect_relay, get_events, publish_event,
           check_relays, add_user, switch_user, list_users
"""

import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from mcp.server.fastmcp import FastMCP
from mcp_helpers import add_code_updater

from nostr_client import NostrClient, pubkey_from_seckey
from user_store import UserStore
from aliases import AliasStore

CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.json")
USERS_PATH = os.path.join(os.path.dirname(__file__), "users.json")
ALIASES_PATH = os.path.join(os.path.dirname(__file__), "aliases.json")


def load_config() -> dict:
    with open(CONFIG_PATH) as f:
        return json.load(f)


config = load_config()
mcp = FastMCP("mcp-nostr")
add_code_updater(mcp, __file__)

client = NostrClient(config.get("relays", []))
store = UserStore(USERS_PATH)
aliases = AliasStore(ALIASES_PATH)


@mcp.tool()
async def connect_relay(url: str) -> str:
    """
    Nawiązuje połączenie WebSocket z relay Nostr.

    Args:
        url: URL relay (np. 'wss://relay.damus.io')
    """
    return await client.connect(url)


@mcp.tool()
async def disconnect_relay(url: str) -> str:
    """
    Rozłącza połączenie z relay Nostr.

    Args:
        url: URL relay do rozłączenia
    """
    return await client.disconnect(url)


@mcp.tool()
async def get_events(filter: dict, url: str | list[str] | None = None) -> str:
    """
    Pobiera eventy z relay według dowolnego filtru Nostr.
    Wyniki są automatycznie skracane: hasze 64-znakowe zastępowane są krótkimi aliasami
    (#e1, #e2 dla event id; #p1, #p2 dla pubkey). Nazwane aliasy (np. #jack) mają priorytet.
    W filtrze możesz używać aliasów zamiast pełnych haszy, np. {"authors": ["#jack"]}.
    Użyj list_aliases() żeby zobaczyć dostępne aliasy, set_alias() żeby dodać nazwany.

    Args:
        filter: Filtr Nostr, np. {"authors": ["#jack"], "kinds": [1], "limit": 20}
        url: URL relay lub lista URL-i. Pominięty → relaye z config.json (wyniki łączone i
             deduplikowane po id). Relaye łączone są w razie potrzeby automatycznie.
    """
    events = await client.get_events(url, filter, aliases)
    if not events:
        return "Brak eventów spełniających filtr."
    return json.dumps(events, ensure_ascii=False, indent=2)


@mcp.tool()
async def publish_event(event: dict, url: str | list[str] | None = None) -> str:
    """
    Podpisuje i publikuje event na relay jako aktywny użytkownik.
    Przyjmuje niepodpisany event — serwer uzupełnia pubkey, id i sig.
    W tagach możesz używać aliasów zamiast pełnych haszy, np. ["e", "#e15"] lub ["p", "#jack"].
    Po udanej publikacji event otrzymuje alias (np. #e17) dostępny w kolejnych wywołaniach.

    Args:
        event: Niepodpisany event, np. {"kind": 1, "content": "Hello!", "tags": [["e", "#e15"]]}
        url: URL relay lub lista URL-i. Pominięty → broadcast na relaye z config.json. Ten sam
             podpisany event trafia na każdy relay; relaye łączone są automatycznie.
    """
    _, user = store.get_active()
    return await client.publish(url, event, user["pubkey"], user["privkey"], aliases)


@mcp.tool()
async def check_relays(urls: list[str], filter: dict | None = None) -> str:
    """
    Odpytuje listę relayów przez NIP-11. Zwraca name, supported_nips, payment_required, auth_required.
    Opcjonalny filtr zawęża wyniki — obsługiwane klucze:
      required_nips: [50]          — tylko relay obsługujące podane NIPy
      payment_required: false      — tylko darmowe
      auth_required: false         — tylko bez wymaganego logowania
    Przykład: {"required_nips": [50], "payment_required": false, "auth_required": false}

    Args:
        urls: Lista URL-i relayów (wss:// lub ws://)
        filter: Opcjonalny filtr pól NIP-11
    """
    results = await client.check_relays(urls, filter)
    return json.dumps(results, ensure_ascii=False, indent=2)


@mcp.tool()
def add_user(name: str) -> str:
    """
    Tworzy nowego użytkownika z wygenerowaną parą kluczy secp256k1.
    Jeśli to pierwszy użytkownik, automatycznie staje się aktywnym.

    Args:
        name: Nazwa użytkownika
    """
    seckey = os.urandom(32)
    return store.add(name, seckey.hex(), pubkey_from_seckey(seckey))


@mcp.tool()
def switch_user(name: str) -> str:
    """
    Przełącza aktywnego użytkownika.

    Args:
        name: Nazwa użytkownika
    """
    return store.switch(name)


@mcp.tool()
def set_alias(name: str, hash: str) -> str:
    """
    Ustawia nazwany alias dla hasha (pubkey lub event id).
    Po ustawieniu możesz używać #nazwa zamiast pełnego hasha wszędzie
    gdzie przyjmowane są hasze — np. w filtrach get_events.
    Przykład: set_alias("jack", "82341f88...") → {"authors": ["#jack"]}

    Args:
        name: Nazwa aliasu (bez #), np. "jack"
        hash: Pełny hash hex (64 znaki) — pubkey lub event id
    """
    return aliases.set_named(name, hash)


@mcp.tool()
def list_aliases() -> str:
    """
    Wyświetla wszystkie aliasy — nazwane (ręczne) i auto-generowane.
    Auto: #e1/#e2... dla event id, #p1/#p2... dla pubkey.
    Nazwane mają priorytet i są czytelniejsze — dodawaj je przez set_alias().
    """
    return aliases.list_all()


@mcp.tool()
def list_users() -> str:
    """Zwraca listę użytkowników wraz z ich pubkey. Aktywny jest oznaczony."""
    return store.list_all()


if __name__ == "__main__":
    import uvicorn
    host = config.get("host", "127.0.0.1")
    port = config.get("port", 8010)
    print(f"------------------ mcp-nostr @ http://{host}:{port}/mcp", flush=True)
    uvicorn.run(mcp.streamable_http_app(), host=host, port=port)
