"""
tests/test_session_api_661.py — Regression tests for #661.

Bug 1: POST /api/v1/sessions returns 405 → should return 400 with guidance.
Bug 2: GET /api/v1/sessions?owner_id=X ignores the owner_id filter.
"""
from __future__ import annotations

from contextlib import contextmanager
from typing import Any, Dict, List, Optional
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

pytest.importorskip("fastapi", reason="fastapi not installed")
pytest.importorskip("httpx", reason="httpx not installed")

from fastapi.testclient import TestClient

PREFIX = "/api/v1"


def _make_mock_plugin(owner_id: str = "owner-test") -> MagicMock:
    plugin = MagicMock()
    plugin.config = MagicMock()
    plugin.config.owner_id = owner_id
    plugin.storage = MagicMock()
    plugin.storage.initialize = AsyncMock()
    plugin.storage.close = AsyncMock()

    # list_sessions returns different data per owner_id
    async def _list_sessions(owner_id="default", limit=50, offset=0):
        if owner_id == "alice":
            return [{"id": "sess-alice-1", "session_id": "alice-s1", "owner_id": "alice", "status": "completed"}]
        elif owner_id == "bob":
            return [
                {"id": "sess-bob-1", "session_id": "bob-s1", "owner_id": "bob", "status": "active"},
                {"id": "sess-bob-2", "session_id": "bob-s2", "owner_id": "bob", "status": "completed"},
            ]
        else:
            return [{"id": "sess-default", "session_id": "default-s1", "owner_id": "default", "status": "active"}]

    plugin.storage.list_sessions = AsyncMock(side_effect=_list_sessions)

    async def _count_sessions(owner_id="default"):
        if owner_id == "alice":
            return 1
        elif owner_id == "bob":
            return 2
        return 1

    plugin.storage.count_sessions = AsyncMock(side_effect=_count_sessions)

    plugin.wake = AsyncMock(return_value=MagicMock(session_id="s1", cornerstones_loaded=0, errors=[]))
    plugin.sleep = AsyncMock(return_value=MagicMock(session_id="s1", memories_summarised=0, errors=[]))
    plugin.dream = AsyncMock(return_value=MagicMock(
        steps_completed=0, curiosity_seeds=0, claims_consolidated=0, journal="", errors=[],
    ))
    plugin.llm = MagicMock()
    return plugin


@contextmanager
def _make_client(api_key: Optional[str] = None, owner_id: str = "owner-test"):
    from cortex.api.http_server import create_app
    mock_plugin = _make_mock_plugin(owner_id=owner_id)
    app = create_app(api_key=api_key)
    with patch("cortex.api.plugin.CortexPlugin") as MockClass:
        MockClass.return_value = mock_plugin
        with TestClient(app, raise_server_exceptions=False) as tc:
            tc._mock_plugin = mock_plugin
            yield tc, mock_plugin


class TestPostSessions661:
    """Bug 1: POST /api/v1/sessions should return 400, not 405."""

    def test_post_sessions_returns_400(self):
        with _make_client() as (tc, _):
            resp = tc.post(f"{PREFIX}/sessions", json={})
        assert resp.status_code == 400, f"Expected 400, got {resp.status_code}: {resp.text}"

    def test_post_sessions_has_guidance(self):
        with _make_client() as (tc, _):
            resp = tc.post(f"{PREFIX}/sessions", json={})
        data = resp.json()
        assert "wake" in data.get("detail", "").lower() or "wake" in data.get("see", "").lower(), (
            f"Response should mention /sessions/wake, got: {data}"
        )

    def test_post_sessions_not_405(self):
        """Explicit regression: must NOT be 405."""
        with _make_client() as (tc, _):
            resp = tc.post(f"{PREFIX}/sessions", json={})
        assert resp.status_code != 405, "POST /sessions must not return 405 anymore"


class TestGetSessionsOwnerFilter661:
    """Bug 2: GET /api/v1/sessions?owner_id=X should filter by owner_id."""

    def test_owner_id_filter_alice(self):
        with _make_client() as (tc, mock_plugin):
            resp = tc.get(f"{PREFIX}/sessions?owner_id=alice")
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] == 1
        assert len(data["sessions"]) == 1
        assert data["sessions"][0]["owner_id"] == "alice"

    def test_owner_id_filter_bob(self):
        with _make_client() as (tc, mock_plugin):
            resp = tc.get(f"{PREFIX}/sessions?owner_id=bob")
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] == 2
        assert len(data["sessions"]) == 2

    def test_owner_id_passed_to_storage(self):
        """Verify the owner_id query param is actually forwarded to storage.list_sessions."""
        with _make_client() as (tc, mock_plugin):
            resp = tc.get(f"{PREFIX}/sessions?owner_id=alice")
        assert resp.status_code == 200
        # Check that list_sessions was called with owner_id="alice"
        call_kwargs = mock_plugin.storage.list_sessions.call_args
        assert call_kwargs is not None, "list_sessions was not called"
        # Could be positional or keyword
        if call_kwargs.kwargs:
            assert call_kwargs.kwargs.get("owner_id") == "alice", (
                f"list_sessions called with wrong owner_id: {call_kwargs.kwargs}"
            )

    def test_owner_id_count_matches_filter(self):
        """count_sessions should also use the filtered owner_id."""
        with _make_client() as (tc, mock_plugin):
            resp = tc.get(f"{PREFIX}/sessions?owner_id=bob")
        assert resp.status_code == 200
        count_kwargs = mock_plugin.storage.count_sessions.call_args
        assert count_kwargs is not None
        if count_kwargs.kwargs:
            assert count_kwargs.kwargs.get("owner_id") == "bob"

    def test_default_owner_when_no_param(self):
        """Without owner_id param, should fall back to config/auth default."""
        with _make_client() as (tc, mock_plugin):
            resp = tc.get(f"{PREFIX}/sessions")
        assert resp.status_code == 200
        # Should use the default resolution (request.state.owner_id or "default")
        call_kwargs = mock_plugin.storage.list_sessions.call_args
        assert call_kwargs is not None
