"""Tests for the generic webhook hooks."""

import json

import pytest
from aiohttp import web
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop

from roohai.webhook import configure, generate_api_key, rest_hook, rest_stream_hook, ws_stream_hook


# ── Fake customer server ─────────────────────────────────────────────────────

async def handle_batch(request: web.Request) -> web.Response:
    """Fake REST endpoint — returns JSON response."""
    data = await request.json()
    reply = f"Hello! You said: {data['message']}"
    return web.json_response({"response": reply})


async def handle_batch_with_auth(request: web.Request) -> web.Response:
    """Fake REST endpoint that requires x-roohai-key."""
    api_key = request.headers.get("x-roohai-key", "")
    if api_key != "rh-test123":
        return web.Response(status=401, text="Unauthorized")
    data = await request.json()
    return web.json_response({"response": f"Authed: {data['message']}"})


async def handle_stream(request: web.Request) -> web.StreamResponse:
    """Fake SSE streaming endpoint."""
    data = await request.json()
    words = f"Streamed reply to {data['message']}".split()

    resp = web.StreamResponse()
    resp.content_type = "text/event-stream"
    await resp.prepare(request)

    for word in words:
        chunk = json.dumps({"text": word + " "})
        await resp.write(f"data: {chunk}\n\n".encode())
    await resp.write(b"data: [DONE]\n\n")
    return resp


async def handle_ws(request: web.Request) -> web.WebSocketResponse:
    """Fake WebSocket endpoint — validates x-roohai-key in payload, sends response word by word."""
    ws = web.WebSocketResponse()
    await ws.prepare(request)

    msg = await ws.receive_json()

    # Validate API key from payload
    api_key = msg.get("x-roohai-key")
    if api_key and api_key != "rh-test123":
        await ws.send_json({"error": "Unauthorized"})
        await ws.close()
        return ws

    words = f"WS reply to {msg['message']}".split()

    for word in words:
        await ws.send_json({"text": word + " "})
    await ws.send_json({"done": True})
    await ws.close()
    return ws


_captured_payloads: list[dict] = []
_captured_headers: list[dict] = []


async def handle_capture(request: web.Request) -> web.Response:
    """Captures the full payload and headers for assertion."""
    data = await request.json()
    _captured_payloads.clear()
    _captured_headers.clear()
    _captured_payloads.append(data)
    _captured_headers.append(dict(request.headers))
    return web.json_response({"response": "ok"})


def create_app() -> web.Application:
    app = web.Application()
    app.router.add_post("/chat", handle_batch)
    app.router.add_post("/chat/auth", handle_batch_with_auth)
    app.router.add_post("/chat/stream", handle_stream)
    app.router.add_post("/capture", handle_capture)
    app.router.add_get("/ws/chat", handle_ws)
    return app


# ── Tests ─────────────────────────────────────────────────────────────────────

class TestApiKeyGeneration(AioHTTPTestCase):
    async def get_application(self):
        return create_app()

    def test_generate_api_key_format(self):
        key = generate_api_key()
        assert key.startswith("rh-")
        assert len(key) == 27  # "rh-" + 24 chars
        # Only lowercase alphanumeric after prefix
        assert key[3:].isalnum()
        assert key[3:].islower() or key[3:].isdigit()

    def test_generate_api_key_unique(self):
        keys = {generate_api_key() for _ in range(100)}
        assert len(keys) == 100  # All unique


class TestRestHook(AioHTTPTestCase):
    async def get_application(self):
        return create_app()

    @unittest_run_loop
    async def test_batch_hook(self):
        url = f"http://localhost:{self.server.port}/chat"
        configure(url=url)

        result = await rest_hook("hello world")
        assert result == "Hello! You said: hello world"

    @unittest_run_loop
    async def test_batch_hook_with_api_key(self):
        url = f"http://localhost:{self.server.port}/chat/auth"
        configure(url=url, api_key="rh-test123")

        result = await rest_hook("secure message")
        assert result == "Authed: secure message"

    @unittest_run_loop
    async def test_batch_hook_auth_fails(self):
        url = f"http://localhost:{self.server.port}/chat/auth"
        configure(url=url, api_key="rh-wrongkey")

        with pytest.raises(RuntimeError, match="401"):
            await rest_hook("should fail")

    @unittest_run_loop
    async def test_api_key_sent_as_header(self):
        """Verify x-roohai-key is sent as HTTP header for REST."""
        url = f"http://localhost:{self.server.port}/capture"
        configure(url=url, api_key="rh-test123")

        await rest_hook("test")
        assert len(_captured_headers) == 1
        # aiohttp lowercases custom header names in the multidict
        headers_lower = {k.lower(): v for k, v in _captured_headers[0].items()}
        assert headers_lower.get("x-roohai-key") == "rh-test123"

    @unittest_run_loop
    async def test_batch_hook_passes_session_id(self):
        """Verify session_id is sent in payload."""
        url = f"http://localhost:{self.server.port}/capture"
        configure(url=url)

        await rest_hook("test", session_id="sess-abc-123")
        assert len(_captured_payloads) == 1
        assert _captured_payloads[0]["session_id"] == "sess-abc-123"
        assert _captured_payloads[0]["message"] == "test"

    @unittest_run_loop
    async def test_no_url_raises(self):
        configure(url=None)
        with pytest.raises(ValueError, match="webhook url not configured"):
            await rest_hook("test")


class TestRestStreamHook(AioHTTPTestCase):
    async def get_application(self):
        return create_app()

    @unittest_run_loop
    async def test_sse_streaming(self):
        url = f"http://localhost:{self.server.port}/chat/stream"
        configure(url=url)

        chunks = []
        async for chunk in rest_stream_hook("hello"):
            chunks.append(chunk)

        full = "".join(chunks)
        assert "Streamed" in full
        assert "hello" in full
        assert len(chunks) > 1  # Must be multiple chunks


class TestWsStreamHook(AioHTTPTestCase):
    async def get_application(self):
        return create_app()

    @unittest_run_loop
    async def test_websocket_streaming(self):
        ws_url = f"http://localhost:{self.server.port}/ws/chat"
        configure(ws_url=ws_url, api_key="rh-test123")

        chunks = []
        async for chunk in ws_stream_hook("hello"):
            chunks.append(chunk)

        full = "".join(chunks)
        assert "WS reply" in full
        assert "hello" in full
        assert len(chunks) > 1  # Must be multiple chunks

    @unittest_run_loop
    async def test_ws_payload_includes_api_key(self):
        """Verify x-roohai-key is included in the WebSocket payload."""
        ws_url = f"http://localhost:{self.server.port}/ws/chat"
        configure(ws_url=ws_url, api_key="rh-test123")

        chunks = []
        async for chunk in ws_stream_hook("test"):
            chunks.append(chunk)

        # If auth had failed, the WS handler would have sent {"error": ...}
        # instead of word chunks — so getting chunks means the key was valid
        assert len(chunks) > 0

    @unittest_run_loop
    async def test_ws_url_auto_converts_http(self):
        """ws_stream_hook should convert http:// to ws:// automatically."""
        ws_url = f"http://localhost:{self.server.port}/ws/chat"
        configure(ws_url=ws_url)

        chunks = []
        async for chunk in ws_stream_hook("test"):
            chunks.append(chunk)

        assert len(chunks) > 0
