"""SYN Link — SSE (Server-Sent Events) client for real-time message delivery.

Cheaper alternative to WebSocket — uses a simple HTTP stream (text/event-stream).
Same token exchange and auto-reconnect pattern as WebSocket.
"""

import asyncio
import json
import logging
import random
from typing import Callable, Optional

import httpx

from syn_link.types import RawIncomingMessage
from syn_link.crypto import sign_request

logger = logging.getLogger("syn_link")


class SSEClient:
    """Persistent SSE connection with auto-reconnect."""

    def __init__(
        self,
        relay_url: str,
        agent_id: str,
        api_key: str,
        on_message: Callable[[RawIncomingMessage], None],
        signing_secret_key: str = "",
    ):
        self.relay_url = relay_url.rstrip("/")
        self.agent_id = agent_id
        self.api_key = api_key
        self.signing_secret_key = signing_secret_key
        self.on_message = on_message
        self._task: Optional[asyncio.Task] = None
        self._should_reconnect = True
        self._reconnect_attempt = 0

    async def _fetch_token(self) -> str:
        """Exchange credentials for a short-lived token (same as WebSocket flow)."""
        url = f"{self.relay_url}/v1/ws/token"
        headers: dict[str, str] = {"Content-Type": "application/json"}
        if self.signing_secret_key:
            sig_headers = sign_request("POST", "/v1/ws/token", None, self.agent_id, self.signing_secret_key)
            headers.update(sig_headers)
        else:
            headers["X-API-Key"] = self.api_key
        async with httpx.AsyncClient() as http:
            resp = await http.post(url, json={}, headers=headers)
            resp.raise_for_status()
            return resp.json()["token"]

    async def start(self) -> None:
        """Start the SSE connection loop in the background."""
        self._should_reconnect = True
        self._reconnect_attempt = 0
        self._task = asyncio.create_task(self._connection_loop())

    async def stop(self) -> None:
        """Stop the SSE connection and cancel reconnects."""
        self._should_reconnect = False
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass
            self._task = None

    async def _connection_loop(self) -> None:
        while self._should_reconnect:
            try:
                token = await self._fetch_token()
                sse_url = f"{self.relay_url}/v1/sse?agent_id={self.agent_id}&token={token}"

                async with httpx.AsyncClient(timeout=None) as http:
                    async with http.stream("GET", sse_url, headers={"Accept": "text/event-stream"}) as resp:
                        if resp.status_code != 200:
                            raise Exception(f"SSE connection failed: {resp.status_code}")

                        logger.info("SSE connected")
                        self._reconnect_attempt = 0

                        buffer = ""
                        async for chunk in resp.aiter_text():
                            buffer += chunk
                            events = buffer.split("\n\n")
                            buffer = events.pop()  # Keep incomplete event

                            for event in events:
                                if not event.strip():
                                    continue
                                self._handle_event(event)

            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.warning(f"SSE error: {e}")

            if self._should_reconnect:
                delay = min(2 ** self._reconnect_attempt, 60) + random.random()
                self._reconnect_attempt += 1
                logger.info(f"Reconnecting in {delay:.1f}s (attempt {self._reconnect_attempt})")
                await asyncio.sleep(delay)

    def _handle_event(self, raw: str) -> None:
        event_type = "message"
        data = ""

        for line in raw.split("\n"):
            if line.startswith("event: "):
                event_type = line[7:].strip()
            elif line.startswith("data: "):
                data += line[6:]
            elif line.startswith("data:"):
                data += line[5:]

        if event_type == "message" and data:
            try:
                parsed = json.loads(data)
                msg = RawIncomingMessage(**parsed)
                self.on_message(msg)
            except Exception:
                pass  # Ignore malformed events
        # "connected" and "ping" events are just keepalive — ignore
