"""
=========================================
The single class developers interact with.

Quickstart
──────────
    from nees_gp import Client

    client = Client(api_key="your-key")
    response = client.chat("Mujhe kuch samajh nahi aa raha")
    print(response.text)
    print(response.intent)         # "confusion"
    print(response.urgency)        # "medium"
    print(response.governance_clean)  # True

Context manager
───────────────
    with Client(api_key="your-key") as client:
        response = client.chat("Help me decide")
        print(response.text)

Environment variable shortcut
──────────────────────────────
    export NEES_API_KEY="your-key"

    from nees_gp import Client
    client = Client()              # reads NEES_API_KEY automatically
"""

from __future__ import annotations

import logging
from typing import Any, Optional

from .config import SDKConfig
from .exceptions import NEESError
from .models import (
    ChatResponse, DriftReport, HealthResponse,
    ProposalResponse,
)
from .session import Session
from .transport import Transport
from .utils import resolve_api_key

logger = logging.getLogger("nees_gp_sdk.client")


class Client:
    """
    NEES GP SDK client.

    Parameters
    ──────────
    api_key         API key (Bearer token). Falls back to NEES_API_KEY env var.
    base_url        NEES GP server URL. Default: https://nees-gp.onrender.com
    timeout         HTTP timeout in seconds. Default: 30
    max_retries     Retries on transient errors. Default: 2
    authority_state Governance authority state A | B | C. Default: B
    session_id      Resume a known session by ID. Default: auto-generated UUID.
    verify_ssl      Verify SSL certificates. Default: True.

    All methods raise subclasses of NEESError on failure.
    """

    def __init__(
        self,
        api_key:         Optional[str] = None,
        base_url:        str = "https://nees-gp.onrender.com",
        timeout:         float = 30,
        max_retries:     int = 2,
        authority_state: str = "B",
        session_id:      Optional[str] = None,
        verify_ssl:      bool = True,
    ) -> None:
        resolved_key = resolve_api_key(api_key)
        self._config = SDKConfig(
            api_key=resolved_key,
            base_url=base_url,
            timeout=timeout,
            max_retries=max_retries,
            authority_state=authority_state,
            verify_ssl=verify_ssl,
        )
        self._transport = Transport(self._config)
        self._session   = Session(session_id=session_id)
        logger.debug("Client initialised | base_url=%s session=%s", base_url, self._session)

    # ─────────────────────────────────────────────────────────────────────────
    # Core interface
    # ─────────────────────────────────────────────────────────────────────────

    def chat(
        self,
        message:    str,
        history:    Optional[list[dict[str, str]]] = None,
        session_id: Optional[str] = None,
    ) -> ChatResponse:
        """
        Send a message through the NEES GP governed chat pipeline.

        The client automatically maintains conversation history across
        calls — you do not need to pass history manually for typical use.
        Pass `history` explicitly only to inject external context.

        Args:
            message     User message text (max 2000 characters).
            history     Optional explicit history override.
                        List of {"role": "user"|"assistant", "content": "..."}.
                        If None, the session's accumulated history is used.
            session_id  Override the session ID for this request only.

        Returns:
            ChatResponse with text, governance metadata, and trace_id.

        Raises:
            AuthError         — invalid API key
            TimeoutError      — request exceeded timeout
            GovernanceError   — server pipeline not initialised
            ValidationError   — bad request payload
            NEESAPIError      — other server error
        """
        payload: dict[str, Any] = {
            "message":    message,
            "history":    history if history is not None else self._session.history_as_dicts(),
            "session_id": session_id or self._session.session_id,
        }
        raw  = self._transport.post("/v1/proxy/chat", json=payload)
        resp = ChatResponse.from_dict(raw)

        # Record the exchange in session history
        self._session.record(message, resp.text)
        logger.debug("chat: intent=%s urgency=%s clean=%s", resp.intent, resp.urgency, resp.governance_clean)
        return resp

    # ─────────────────────────────────────────────────────────────────────────
    # Governance pipeline operations
    # ─────────────────────────────────────────────────────────────────────────

    def propose(
        self,
        name:     str,
        category: str,
        payload:  Optional[dict[str, Any]] = None,
        principal: Optional[str] = None,
    ) -> ProposalResponse:
        """
        Create a governance proposal via POST /propose.
        Evaluates the action through G1–G7 gates without executing it.

        Args:
            name      Human-readable action label.
            category  ActionCategory: READ | WRITE | EXTERNAL_CALL |
                      MEMORY_OP | DELEGATE | SYSTEM_CONFIG |
                      IRREVERSIBLE | SAFETY_CHANGE
            payload   Arbitrary action parameters dict.
            principal Optional principal identifier.

        Returns:
            ProposalResponse with verdict, risk_level, and admissibility detail.
        """
        body: dict[str, Any] = {
            "name":     name,
            "category": category.upper(),
            "payload":  payload or {},
        }
        if principal:
            body["principal"] = principal
        raw = self._transport.post("/propose", json=body)
        return ProposalResponse.from_dict(raw)

    # ─────────────────────────────────────────────────────────────────────────
    # Monitoring
    # ─────────────────────────────────────────────────────────────────────────

    def health(self) -> HealthResponse:
        """
        Check NEES GP server liveness via GET /health.

        Returns:
            HealthResponse with status, uptime, authority_state.

        Raises:
            GovernanceError — server is unhealthy (503)
        """
        raw = self._transport.get("/health")
        return HealthResponse.from_dict(raw)

    def drift(self) -> DriftReport:
        """
        Get the current drift report via GET /trajectory.

        Returns:
            DriftReport with status, anomaly_score, drift_magnitude.
        """
        raw = self._transport.get("/trajectory")
        return DriftReport.from_dict(raw)

    def ledger(self, n: int = 20) -> list[dict[str, Any]]:
        """
        Fetch the last N audit entries from the governance ledger.

        Args:
            n   Number of entries to return (max 200).

        Returns:
            List of raw ledger entry dicts.
        """
        raw = self._transport.get("/ledger", params={"n": min(n, 200)})
        return raw.get("entries", [])

    def policies(self) -> list[dict[str, Any]]:
        """
        List all currently loaded YAML governance policies.

        Returns:
            List of policy summary dicts.
        """
        raw = self._transport.get("/policies")
        return raw.get("rules", [])

    # ─────────────────────────────────────────────────────────────────────────
    # Session management
    # ─────────────────────────────────────────────────────────────────────────

    def reset_session(self) -> str:
        """
        Clear conversation history and generate a new session_id.
        Returns the new session_id.
        """
        self._session.reset()
        logger.debug("Session reset: new id=%s", self._session.session_id)
        return self._session.session_id

    def resume_session(
        self,
        session_id: str,
        history: Optional[list[dict[str, str]]] = None,
    ) -> None:
        """
        Resume a previously known session.

        Args:
            session_id  The session_id to resume.
            history     Optional history to restore. If None, starts empty.
        """
        self._session.seed(session_id, history)
        logger.debug("Session resumed: id=%s turns=%d", session_id, self._session.turn_count // 2)

    @property
    def session_id(self) -> str:
        """Current session identifier."""
        return self._session.session_id

    @property
    def history(self) -> list[dict[str, str]]:
        """Current conversation history as a list of dicts."""
        return self._session.history_as_dicts()

    # ─────────────────────────────────────────────────────────────────────────
    # Context manager support
    # ─────────────────────────────────────────────────────────────────────────

    def __enter__(self) -> "Client":
        return self

    def __exit__(self, *_: Any) -> None:
        self.close()

    def close(self) -> None:
        """Release the underlying HTTP connections."""
        self._transport.close()

    def __repr__(self) -> str:
        return (
            f"Client(base_url={self._config.base_url!r}, "
            f"session={self._session!r})"
        )
