"""
Story 7.03 — Test Suite
=======================
GET /context/{session_id}

Verifies that the endpoint:
  - Returns 200 with context_xml + hydrated=True when Redis key exists
  - Returns 200 with hydrated=False when key is absent (never 404)
  - Echoes session_id back in every response
  - Decodes bytes from Redis into a utf-8 string
  - Uses the exact Redis key pattern aiva:context:{session_id}
  - Returns hydrated=False when no Redis client is injected (None stub)

BB Tests: BB1, BB2, BB3, BB4
WB Tests: WB1, WB2, WB3
"""

from __future__ import annotations

from unittest.mock import MagicMock

import pytest
from fastapi.testclient import TestClient

from api.aiva_memory_api import app, get_redis_client

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

SAMPLE_XML = (
    "<ROYAL_CHAMBER_CONTEXT>"
    "<session_id>sess-abc</session_id>"
    "<memory>Deep memory</memory>"
    "</ROYAL_CHAMBER_CONTEXT>"
)


def make_redis_with_key(session_id: str, value: str | bytes) -> MagicMock:
    """Return a mock Redis client that has `aiva:context:{session_id}` set."""
    redis = MagicMock()
    redis.get.side_effect = (
        lambda key: value if key == f"aiva:context:{session_id}" else None
    )
    return redis


def make_empty_redis() -> MagicMock:
    """Return a mock Redis client where every .get() returns None."""
    redis = MagicMock()
    redis.get.return_value = None
    return redis


# ---------------------------------------------------------------------------
# BB1 — Active session with preloaded context → context_xml returned, hydrated=True
# ---------------------------------------------------------------------------


def test_bb1_active_session_returns_context_xml_and_hydrated_true():
    """
    BB1: When the Redis key aiva:context:{session_id} exists and holds a
    string value the endpoint returns HTTP 200 with the XML and hydrated=True.
    """
    redis_mock = make_redis_with_key("sess-abc", SAMPLE_XML)

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        response = client.get("/context/sess-abc")
        assert response.status_code == 200
        body = response.json()
        assert body["context_xml"] == SAMPLE_XML
        assert body["hydrated"] is True
    finally:
        app.dependency_overrides.clear()


# ---------------------------------------------------------------------------
# BB2 — Missing key in Redis → 200 with hydrated=False (NOT 404)
# ---------------------------------------------------------------------------


def test_bb2_missing_key_returns_200_with_hydrated_false():
    """
    BB2: When the Redis key does not exist the endpoint MUST return HTTP 200,
    not 404.  hydrated must be False and context_xml must be an empty string.
    """
    redis_mock = make_empty_redis()

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        response = client.get("/context/nonexistent-session")
        assert response.status_code == 200
        body = response.json()
        assert body["hydrated"] is False
        assert body["context_xml"] == ""
    finally:
        app.dependency_overrides.clear()


# ---------------------------------------------------------------------------
# BB3 — session_id echoed back in response
# ---------------------------------------------------------------------------


def test_bb3_session_id_echoed_in_response_when_hydrated():
    """BB3: The response always echoes back the session_id from the path."""
    redis_mock = make_redis_with_key("echo-test-42", SAMPLE_XML)

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        response = client.get("/context/echo-test-42")
        assert response.status_code == 200
        assert response.json()["session_id"] == "echo-test-42"
    finally:
        app.dependency_overrides.clear()


def test_bb3_session_id_echoed_when_not_hydrated():
    """BB3 (variant): session_id is also echoed when the key is absent."""
    redis_mock = make_empty_redis()

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        response = client.get("/context/ghost-session")
        assert response.status_code == 200
        assert response.json()["session_id"] == "ghost-session"
    finally:
        app.dependency_overrides.clear()


# ---------------------------------------------------------------------------
# BB4 — No Redis client (None injected) → hydrated=False
# ---------------------------------------------------------------------------


def test_bb4_no_redis_client_returns_hydrated_false():
    """
    BB4: When the Redis dependency returns None (default stub behaviour in
    production before real client is wired) the endpoint gracefully degrades:
    HTTP 200 with hydrated=False and empty context_xml.
    """
    app.dependency_overrides[get_redis_client] = lambda: None
    try:
        client = TestClient(app)
        response = client.get("/context/any-session")
        assert response.status_code == 200
        body = response.json()
        assert body["hydrated"] is False
        assert body["context_xml"] == ""
    finally:
        app.dependency_overrides.clear()


# ---------------------------------------------------------------------------
# WB1 — Redis read uses exact key aiva:context:{session_id}
# ---------------------------------------------------------------------------


def test_wb1_redis_key_pattern_is_correct():
    """
    WB1: Verifies that redis.get() is called with the exact key
    `aiva:context:{session_id}` — not a wrong prefix or suffix.
    """
    redis_mock = MagicMock()
    redis_mock.get.return_value = SAMPLE_XML

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        client.get("/context/sess-wb1")
        redis_mock.get.assert_called_once_with("aiva:context:sess-wb1")
    finally:
        app.dependency_overrides.clear()


# ---------------------------------------------------------------------------
# WB2 — Bytes from Redis decoded to utf-8 string
# ---------------------------------------------------------------------------


def test_wb2_bytes_from_redis_decoded_to_string():
    """
    WB2: When Redis returns bytes (which is the default Redis-py behaviour)
    the endpoint must decode them to a utf-8 string before returning.
    """
    xml_bytes = SAMPLE_XML.encode("utf-8")
    redis_mock = make_redis_with_key("sess-bytes", xml_bytes)

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        response = client.get("/context/sess-bytes")
        assert response.status_code == 200
        body = response.json()
        assert isinstance(body["context_xml"], str)
        assert body["context_xml"] == SAMPLE_XML
        assert body["hydrated"] is True
    finally:
        app.dependency_overrides.clear()


# ---------------------------------------------------------------------------
# WB3 — hydrated is a boolean True / False (not truthy string or int)
# ---------------------------------------------------------------------------


def test_wb3_hydrated_is_boolean_true_when_found():
    """WB3a: hydrated must be boolean True (not 1, 'true', etc.)."""
    redis_mock = make_redis_with_key("bool-test", SAMPLE_XML)

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        body = client.get("/context/bool-test").json()
        assert body["hydrated"] is True
        assert isinstance(body["hydrated"], bool)
    finally:
        app.dependency_overrides.clear()


def test_wb3_hydrated_is_boolean_false_when_missing():
    """WB3b: hydrated must be boolean False (not 0, 'false', None, etc.)."""
    redis_mock = make_empty_redis()

    app.dependency_overrides[get_redis_client] = lambda: redis_mock
    try:
        client = TestClient(app)
        body = client.get("/context/no-key").json()
        assert body["hydrated"] is False
        assert isinstance(body["hydrated"], bool)
    finally:
        app.dependency_overrides.clear()
