"""
neuroflow.session -- Session lifecycle management for the NeuroFlow SDK.

Provides the Session class that manages recording sessions on remote
NeuroFlow servers. A session binds a device to a time-bounded recording
with optional real-time processing pipeline and feature extraction.

Usage::

    client = neuroflow.Client(api_url="http://localhost:8000")
    devices = client.discover()
    session = client.start_session(devices[0].id, pipeline="meditation")

    # Stream real-time data
    for data in session.stream(metrics=["focus", "alpha_power"]):
        print(f"Focus: {data.features.get('avg_theta_beta_ratio')}")

    # Stop and get summary
    result = session.stop()
    print(result.features)
"""

from __future__ import annotations

from typing import Any, Iterator, Optional

from neuroflow.models import (
    Features,
    Session as SessionModel,
    SessionData,
    SessionStatus,
    StreamData,
)
from neuroflow.stream import SyncDataStream


class SessionHandle:
    """
    Handle to an active or completed recording session on a NeuroFlow server.

    Provides methods to stream real-time data, stop the session, and
    retrieve recorded data and extracted features. Returned by
    ``Client.start_session()``.

    Args:
        client: The parent Client instance.
        session_info: Session model with initial session state.
    """

    def __init__(self, client: Any, session_info: SessionModel) -> None:
        self._client = client
        self._info = session_info

    @property
    def id(self) -> str:
        """Unique session identifier."""
        return self._info.session_id

    @property
    def device_id(self) -> str:
        """Identifier of the device being recorded."""
        return self._info.device_id

    @property
    def status(self) -> SessionStatus:
        """Current session lifecycle status."""
        return self._info.status

    @property
    def is_active(self) -> bool:
        """Whether the session is currently recording."""
        return self._info.status == SessionStatus.RECORDING

    @property
    def duration_seconds(self) -> float:
        """Session duration in seconds."""
        return self._info.duration_seconds

    @property
    def total_samples(self) -> int:
        """Total EEG samples captured so far."""
        return self._info.total_samples

    @property
    def info(self) -> SessionModel:
        """Full session information model."""
        return self._info

    def refresh(self) -> "SessionHandle":
        """
        Refresh session info from the server.

        Returns:
            Self with updated session information.
        """
        self._info = self._client._get_session_info(self.id)
        return self

    def stream(
        self,
        metrics: Optional[list[str]] = None,
        pipeline_name: str = "",
        downsample_factor: int = 1,
        window_seconds: float = 0.1,
    ) -> Iterator[StreamData]:
        """
        Stream real-time data from this session's device.

        Opens a WebSocket connection and yields StreamData objects as
        the device produces them. The stream continues until ``stop()``
        is called or the iterator is exited.

        Args:
            metrics: List of metric types to include. Valid values:
                "eeg_data", "features", "quality", "processed_data".
                Defaults to ["eeg_data", "features"] if a pipeline is set.
            pipeline_name: Processing pipeline to apply server-side.
                Defaults to the session's pipeline if one was configured.
            downsample_factor: Yield every Nth data window (default 1).
            window_seconds: Data window size in seconds (default 0.1).

        Yields:
            StreamData objects with EEG samples, features, and quality.

        Example::

            for data in session.stream():
                if data.features:
                    print(f"Alpha: {data.features.get('avg_alpha_power')}")
        """
        # Determine channels to subscribe to
        if metrics is None:
            channels = ["eeg_data"]
            if pipeline_name or self._info.pipeline_name:
                channels.append("features")
                channels.append("quality")
        else:
            # Map friendly metric names to stream channels
            channel_map = {
                "eeg": "eeg_data",
                "eeg_data": "eeg_data",
                "features": "features",
                "quality": "quality",
                "processed": "processed_data",
                "processed_data": "processed_data",
                # Also allow specific feature names to trigger features channel
                "focus": "features",
                "alpha_power": "features",
                "theta_beta": "features",
                "meditation": "features",
                "attention": "features",
            }
            channels = list({
                channel_map.get(m, "features") for m in metrics
            })

        # Build WebSocket URL
        api_url = self._client._api_url.rstrip("/")
        ws_scheme = "ws" if api_url.startswith("http://") else "wss"
        host = api_url.replace("http://", "").replace("https://", "")
        ws_url = f"{ws_scheme}://{host}/ws/stream/{self.device_id}"

        effective_pipeline = pipeline_name or self._info.pipeline_name

        sync_stream = SyncDataStream(
            ws_url=ws_url,
            device_id=self.device_id,
            channels=channels,
            pipeline_name=effective_pipeline,
            downsample_factor=downsample_factor,
            window_seconds=window_seconds,
            api_key=self._client._api_key,
        )

        return iter(sync_stream)

    def stop(self) -> SessionModel:
        """
        Stop this recording session.

        Signals the server to stop recording, finalize processing,
        and store the session data.

        Returns:
            Updated session model with final status and statistics.
        """
        self._info = self._client._stop_session(self.id)
        return self._info

    def get_data(self) -> SessionData:
        """
        Retrieve the complete recorded data for this session.

        Only available after the session has been stopped.

        Returns:
            SessionData with EEG data, timestamps, and features.

        Raises:
            RuntimeError: If the session is still recording.
        """
        return self._client._get_session_data(self.id)

    def get_features(self) -> Features:
        """
        Retrieve extracted features for this session.

        Returns features computed by the session's processing pipeline.
        Only available after the session has been stopped and processed.

        Returns:
            Features with categorized neuroscience metrics.
        """
        raw = self._client._get_session_features(self.id)
        return Features.from_raw_features(raw, session_id=self.id)

    def export(self, format: str = "csv") -> bytes:
        """
        Export session data in the specified format.

        Downloads the session data from the server as a file.

        Args:
            format: Export format — "csv", "edf", or "json".

        Returns:
            Raw bytes of the exported file.

        Raises:
            ValueError: If an unsupported format is specified.
            NeuroFlowAPIError: If the server returns an error.

        Example::

            data = session.export("csv")
            with open("recording.csv", "wb") as f:
                f.write(data)
        """
        valid_formats = ("csv", "edf", "json")
        if format not in valid_formats:
            raise ValueError(f"Unsupported format '{format}'. Use one of: {valid_formats}")
        return self._client._export_session(self.id, format)

    def add_marker(self, label: str, timestamp: float = None, metadata: dict = None) -> dict:
        """
        Add an event marker to this session.

        Markers are used for time-locked analysis (ERP, stimulus-response).

        Args:
            label: Human-readable marker label (e.g. "stimulus_onset", "response").
            timestamp: Unix timestamp for the marker. Defaults to server time.
            metadata: Optional metadata dict for additional marker info.

        Returns:
            Marker info dict with marker_id, label, and timestamp.

        Example::

            session.add_marker("stimulus_onset")
            session.add_marker("response", metadata={"correct": True})
        """
        return self._client._add_marker(self.id, label, timestamp, metadata)

    def get_markers(self) -> list[dict]:
        """
        Retrieve all event markers for this session.

        Returns:
            List of marker dicts sorted by timestamp.
        """
        return self._client._get_markers(self.id)

    def delete(self) -> None:
        """
        Delete this session and its data from the server.

        This action is irreversible.
        """
        self._client._delete_session(self.id)
        self._info.status = SessionStatus.FAILED  # Mark as unusable

    def __repr__(self) -> str:
        return (
            f"SessionHandle(id={self.id!r}, device={self.device_id!r}, "
            f"status={self.status.value})"
        )
