"""Memory retrieval from the Flowlines backend."""

from __future__ import annotations

import asyncio
import json
import logging
from typing import TYPE_CHECKING
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

if TYPE_CHECKING:
    from http.client import HTTPResponse

logger = logging.getLogger("flowlines")


class FlowlinesMemoryError(Exception):
    """Raised when memory retrieval from the Flowlines backend fails.

    Attributes:
        status_code: The HTTP status code, or ``None`` for network/parse errors.
    """

    def __init__(self, message: str, *, status_code: int | None = None) -> None:
        super().__init__(message)
        self.status_code = status_code


def _build_url(
    api_endpoint: str,
    *,
    user_id: str,
    session_id: str | None = None,
    agent_id: str | None = None,
    view: str | None = None,
) -> str:
    """Construct the ``get-memory`` URL with query parameters.

    Args:
        api_endpoint: The base API endpoint (e.g. ``https://api.flowlines.ai``).
        user_id: The user ID (required).
        session_id: Optional session ID filter.
        agent_id: Optional agent ID filter.
        view: Optional view name.

    Returns:
        The fully-qualified URL string.
    """
    base = api_endpoint.rstrip("/")
    params: dict[str, str] = {"user_id": user_id}
    if session_id is not None:
        params["session_id"] = session_id
    if agent_id is not None:
        params["agent_id"] = agent_id
    if view is not None:
        params["view"] = view
    return f"{base}/v1/get-memory?{urlencode(params)}"


def fetch_memory(
    *,
    api_key: str,
    api_endpoint: str,
    user_id: str,
    session_id: str | None = None,
    agent_id: str | None = None,
    view: str | None = None,
) -> str | None:
    """Retrieve memory from the Flowlines backend (synchronous).

    Args:
        api_key: The Flowlines API key.
        api_endpoint: The base API endpoint.
        user_id: The user ID (required).
        session_id: Optional session ID filter.
        agent_id: Optional agent ID filter.
        view: Optional view name.

    Returns:
        A JSON string of the ``memory`` object from the response, or
        ``None`` if the server returned HTTP 404.

    Raises:
        FlowlinesMemoryError: On HTTP errors (other than 404), network
            failures, or malformed responses.
    """
    url = _build_url(
        api_endpoint,
        user_id=user_id,
        session_id=session_id,
        agent_id=agent_id,
        view=view,
    )
    request = Request(
        url,
        headers={
            "x-flowlines-api-key": api_key,
            "User-Agent": "flowlines-sdk-py",
        },
    )  # noqa: S310

    try:
        response: HTTPResponse = urlopen(request, timeout=30)  # noqa: S310
    except HTTPError as exc:
        if exc.code == 404:
            logger.debug("Memory request returned HTTP 404 — returning None")
            return None
        body = exc.read().decode("utf-8", errors="replace")
        try:
            detail = json.loads(body).get("error", body)
        except (json.JSONDecodeError, AttributeError):
            detail = body
        msg = f"Memory request failed (HTTP {exc.code}): {detail}"
        raise FlowlinesMemoryError(msg, status_code=exc.code) from exc
    except URLError as exc:
        msg = f"Memory request failed: {exc.reason}"
        raise FlowlinesMemoryError(msg) from exc

    body_bytes = response.read()
    try:
        data: dict[str, object] = json.loads(body_bytes)
    except json.JSONDecodeError as exc:
        msg = "Memory request returned malformed JSON."
        raise FlowlinesMemoryError(msg) from exc

    if "memory" not in data:
        msg = "Memory response missing 'memory' key."
        raise FlowlinesMemoryError(msg)

    return json.dumps(data["memory"])


async def afetch_memory(
    *,
    api_key: str,
    api_endpoint: str,
    user_id: str,
    session_id: str | None = None,
    agent_id: str | None = None,
    view: str | None = None,
) -> str | None:
    """Retrieve memory from the Flowlines backend (asynchronous).

    Delegates to :func:`fetch_memory` via :func:`asyncio.to_thread`.
    See :func:`fetch_memory` for full parameter documentation.

    Returns:
        A JSON string of the ``memory`` object from the response, or
        ``None`` if the server returned HTTP 404.

    Raises:
        FlowlinesMemoryError: On HTTP errors (other than 404), network
            failures, or malformed responses.
    """
    return await asyncio.to_thread(
        fetch_memory,
        api_key=api_key,
        api_endpoint=api_endpoint,
        user_id=user_id,
        session_id=session_id,
        agent_id=agent_id,
        view=view,
    )
