"""Timmy's Nostr identity lifecycle manager.

Manages Timmy's on-network Nostr presence:

- **Kind 0** (NIP-01 profile metadata): name, about, picture, nip05
- **Kind 31990** (NIP-89 handler / NIP-90 capability card): advertises
  Timmy's services so NIP-89 clients can discover him.

Config is read from ``settings`` via pydantic-settings:

    NOSTR_PRIVKEY    — hex private key (required to publish)
    NOSTR_PUBKEY     — hex public key  (auto-derived if missing)
    NOSTR_RELAYS     — comma-separated relay WSS URLs
    NOSTR_NIP05      — NIP-05 identifier e.g. timmy@tower.local
    NOSTR_PROFILE_NAME    — display name (default: "Timmy")
    NOSTR_PROFILE_ABOUT   — "about" text
    NOSTR_PROFILE_PICTURE — avatar URL

Usage
-----
    from infrastructure.nostr.identity import NostrIdentityManager

    manager = NostrIdentityManager()
    result = await manager.announce()
    # {'kind_0': True, 'kind_31990': True, 'relays': {'wss://…': True}}
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass, field
from typing import Any

from config import settings
from infrastructure.nostr.event import build_event
from infrastructure.nostr.keypair import NostrKeypair, load_keypair
from infrastructure.nostr.relay import publish_to_relays

logger = logging.getLogger(__name__)

# Timmy's default capability description for NIP-89/NIP-90
_DEFAULT_CAPABILITIES = {
    "name": "Timmy",
    "about": (
        "Sovereign AI agent — mission control dashboard, task orchestration, "
        "voice NLU, game-state monitoring, and ambient intelligence."
    ),
    "capabilities": [
        "chat",
        "task_orchestration",
        "voice_nlu",
        "game_state",
        "nostr_presence",
    ],
    "nip": [1, 89, 90],
}


@dataclass
class AnnounceResult:
    """Result of a Nostr identity announcement."""

    kind_0_ok: bool = False
    kind_31990_ok: bool = False
    relay_results: dict[str, bool] = field(default_factory=dict)

    @property
    def any_relay_ok(self) -> bool:
        return any(self.relay_results.values())

    def to_dict(self) -> dict[str, Any]:
        return {
            "kind_0": self.kind_0_ok,
            "kind_31990": self.kind_31990_ok,
            "relays": self.relay_results,
        }


class NostrIdentityManager:
    """Manages Timmy's Nostr identity and relay presence.

    Reads configuration from ``settings`` on every call so runtime
    changes to environment variables are picked up automatically.

    All public methods degrade gracefully — they log warnings and return
    False/empty rather than raising exceptions.
    """

    # ── keypair ─────────────────────────────────────────────────────────────

    def get_keypair(self) -> NostrKeypair | None:
        """Return the configured keypair, or None if not configured.

        Derives the public key from the private key if only the private
        key is set.  Returns None (with a warning) if no private key is
        configured.
        """
        privkey = settings.nostr_privkey.strip()
        if not privkey:
            logger.warning(
                "NOSTR_PRIVKEY not configured — Nostr identity unavailable. "
                "Run `timmyctl nostr keygen` to generate a keypair."
            )
            return None
        try:
            return load_keypair(privkey_hex=privkey)
        except Exception as exc:
            logger.warning("Invalid NOSTR_PRIVKEY: %s", exc)
            return None

    # ── relay list ───────────────────────────────────────────────────────────

    def get_relay_urls(self) -> list[str]:
        """Return the configured relay URL list (may be empty)."""
        raw = settings.nostr_relays.strip()
        if not raw:
            return []
        return [url.strip() for url in raw.split(",") if url.strip()]

    # ── Kind 0 — profile ─────────────────────────────────────────────────────

    def build_profile_event(self, keypair: NostrKeypair) -> dict:
        """Build a NIP-01 Kind 0 profile metadata event.

        Reads profile fields from settings:
        ``nostr_profile_name``, ``nostr_profile_about``,
        ``nostr_profile_picture``, ``nostr_nip05``.
        """
        profile: dict[str, str] = {}

        name = settings.nostr_profile_name.strip() or "Timmy"
        profile["name"] = name
        profile["display_name"] = name

        about = settings.nostr_profile_about.strip()
        if about:
            profile["about"] = about

        picture = settings.nostr_profile_picture.strip()
        if picture:
            profile["picture"] = picture

        nip05 = settings.nostr_nip05.strip()
        if nip05:
            profile["nip05"] = nip05

        return build_event(
            kind=0,
            content=json.dumps(profile, ensure_ascii=False),
            keypair=keypair,
        )

    # ── Kind 31990 — NIP-89 capability card ──────────────────────────────────

    def build_capability_event(self, keypair: NostrKeypair) -> dict:
        """Build a NIP-89/NIP-90 Kind 31990 capability handler event.

        Advertises Timmy's services so NIP-89 clients can discover him.
        The ``d`` tag uses the application identifier ``timmy-mission-control``.
        """
        cap = dict(_DEFAULT_CAPABILITIES)
        name = settings.nostr_profile_name.strip() or "Timmy"
        cap["name"] = name

        about = settings.nostr_profile_about.strip()
        if about:
            cap["about"] = about

        picture = settings.nostr_profile_picture.strip()
        if picture:
            cap["picture"] = picture

        nip05 = settings.nostr_nip05.strip()
        if nip05:
            cap["nip05"] = nip05

        tags = [
            ["d", "timmy-mission-control"],
            ["k", "1"],  # handles kind:1 (notes) as a starting point
            ["k", "5600"],  # DVM task request (NIP-90)
            ["k", "5900"],  # DVM general task
        ]

        return build_event(
            kind=31990,
            content=json.dumps(cap, ensure_ascii=False),
            keypair=keypair,
            tags=tags,
        )

    # ── announce ─────────────────────────────────────────────────────────────

    async def announce(self) -> AnnounceResult:
        """Publish Kind 0 profile and Kind 31990 capability card to all relays.

        Returns
        -------
        AnnounceResult
            Contains per-relay success flags and per-event-kind success flags.
            Never raises; all failures are logged at WARNING level.
        """
        result = AnnounceResult()

        keypair = self.get_keypair()
        if keypair is None:
            return result

        relay_urls = self.get_relay_urls()
        if not relay_urls:
            logger.warning("NOSTR_RELAYS not configured — Kind 0 and Kind 31990 not published.")
            return result

        logger.info(
            "Announcing Nostr identity %s to %d relay(s)", keypair.npub[:20], len(relay_urls)
        )

        # Build and publish Kind 0 (profile)
        try:
            kind0 = self.build_profile_event(keypair)
            k0_results = await publish_to_relays(relay_urls, kind0)
            result.kind_0_ok = any(k0_results.values())
            # Merge relay results
            for url, ok in k0_results.items():
                result.relay_results[url] = result.relay_results.get(url, False) or ok
        except Exception as exc:
            logger.warning("Kind 0 publish failed: %s", exc)

        # Build and publish Kind 31990 (capability card)
        try:
            kind31990 = self.build_capability_event(keypair)
            k31990_results = await publish_to_relays(relay_urls, kind31990)
            result.kind_31990_ok = any(k31990_results.values())
            for url, ok in k31990_results.items():
                result.relay_results[url] = result.relay_results.get(url, False) or ok
        except Exception as exc:
            logger.warning("Kind 31990 publish failed: %s", exc)

        if result.any_relay_ok:
            logger.info("Nostr identity announced successfully (npub: %s)", keypair.npub)
        else:
            logger.warning("Nostr identity announcement failed — no relays accepted events")

        return result

    async def publish_profile(self) -> bool:
        """Publish only the Kind 0 profile event.

        Returns True if at least one relay accepted the event.
        """
        keypair = self.get_keypair()
        if keypair is None:
            return False
        relay_urls = self.get_relay_urls()
        if not relay_urls:
            return False
        try:
            event = self.build_profile_event(keypair)
            results = await publish_to_relays(relay_urls, event)
            return any(results.values())
        except Exception as exc:
            logger.warning("Profile publish failed: %s", exc)
            return False
