"""In-memory meeting storage for the NoteFlow gRPC server.

Provides thread-safe in-memory storage using domain entities directly.
Used as fallback when no database is configured.
"""

from __future__ import annotations

import threading
from typing import TYPE_CHECKING

from noteflow.config.constants import ERROR_MSG_MEETING_PREFIX
from noteflow.domain.entities import Meeting, Segment, Summary
from noteflow.domain.value_objects import MeetingState
from noteflow.infrastructure.persistence.memory.repositories import (
    InMemoryIntegrationRepository,
)

if TYPE_CHECKING:
    from collections.abc import Sequence
    from datetime import datetime


class MeetingStore:
    """Thread-safe in-memory meeting storage using domain entities."""

    def __init__(self) -> None:
        """Initialize the store."""
        self._meetings: dict[str, Meeting] = {}
        self._lock = threading.RLock()
        # Shared integration repository for cross-UoW persistence
        self._integrations = InMemoryIntegrationRepository()

    def create(
        self,
        title: str = "",
        metadata: dict[str, str] | None = None,
    ) -> Meeting:
        """Create a new meeting.

        Args:
            title: Optional meeting title.
            metadata: Optional metadata.

        Returns:
            Created meeting.
        """
        meeting = Meeting.create(title=title or "Untitled Meeting", metadata=metadata or {})

        with self._lock:
            self._meetings[str(meeting.id)] = meeting

        return meeting

    def insert(self, meeting: Meeting) -> Meeting:
        """Insert an existing meeting into the store.

        Args:
            meeting: Meeting entity to store.

        Returns:
            Stored meeting.
        """
        with self._lock:
            self._meetings[str(meeting.id)] = meeting
            return meeting

    def get(self, meeting_id: str) -> Meeting | None:
        """Get a meeting by ID.

        Args:
            meeting_id: Meeting ID string.

        Returns:
            Meeting or None if not found.
        """
        with self._lock:
            return self._meetings.get(meeting_id)

    def count_by_state(self, state: MeetingState) -> int:
        """Count meetings in a specific state.

        Args:
            state: Meeting state to count.

        Returns:
            Number of meetings in the specified state.
        """
        with self._lock:
            return sum(m.state == state for m in self._meetings.values())

    def list_all(
        self,
        states: Sequence[MeetingState] | None = None,
        limit: int = 100,
        offset: int = 0,
        sort_desc: bool = True,
        project_id: str | None = None,
    ) -> tuple[list[Meeting], int]:
        """List meetings with optional filtering.

        Args:
            states: Filter by these states (all if None).
            limit: Max meetings per page.
            offset: Pagination offset.
            sort_desc: Sort by created_at descending.

        Returns:
            Tuple of (paginated meeting list, total matching count).
        """
        with self._lock:
            meetings = list(self._meetings.values())

            # Filter by state
            if states:
                state_set = set(states)
                meetings = [m for m in meetings if m.state in state_set]

            # Filter by project if requested
            if project_id:
                meetings = [
                    m for m in meetings if m.project_id is not None and str(m.project_id) == project_id
                ]

            total = len(meetings)

            # Sort
            meetings.sort(key=lambda m: m.created_at, reverse=sort_desc)

            # Paginate
            meetings = meetings[offset : offset + limit]

            return meetings, total

    def find_older_than(self, cutoff: datetime) -> list[Meeting]:
        """Find completed meetings older than cutoff date.

        Args:
            cutoff: Cutoff datetime; meetings ended before this are returned.

        Returns:
            List of meetings with ended_at before cutoff.
        """
        with self._lock:
            return [
                m
                for m in self._meetings.values()
                if m.ended_at is not None and m.ended_at < cutoff
            ]

    def update(self, meeting: Meeting) -> Meeting:
        """Update a meeting in the store.

        Args:
            meeting: Meeting with updated fields.

        Returns:
            Updated meeting.
        """
        with self._lock:
            stored = self._meetings.get(str(meeting.id))
            if stored and stored.version != meeting.version:
                raise ValueError(f"{ERROR_MSG_MEETING_PREFIX}{meeting.id} has been modified concurrently")
            meeting.version += 1
            self._meetings[str(meeting.id)] = meeting
            return meeting

    def add_segment(self, meeting_id: str, segment: Segment) -> Meeting | None:
        """Add a segment to a meeting.

        Args:
            meeting_id: Meeting ID.
            segment: Segment to add.

        Returns:
            Updated meeting or None if not found.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            if meeting is None:
                return None

            meeting.add_segment(segment)
            return meeting

    def fetch_segments(self, meeting_id: str) -> list[Segment]:
        """Fetch a copy of segments for in-memory meeting.

        Args:
            meeting_id: Meeting ID.

        Returns:
            List of segments (copy), or empty list if meeting not found.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            return [] if meeting is None else list(meeting.segments)

    def set_summary(self, meeting_id: str, summary: Summary) -> Meeting | None:
        """Set meeting summary.

        Args:
            meeting_id: Meeting ID.
            summary: Summary to set.

        Returns:
            Updated meeting or None if not found.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            if meeting is None:
                return None

            meeting.summary = summary
            return meeting

    def get_summary(self, meeting_id: str) -> Summary | None:
        """Get meeting summary.

        Args:
            meeting_id: Meeting ID.

        Returns:
            Summary or None if missing.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            return meeting.summary if meeting else None

    def clear_summary(self, meeting_id: str) -> bool:
        """Clear meeting summary.

        Args:
            meeting_id: Meeting ID.

        Returns:
            True if cleared, False if meeting not found or no summary set.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            if meeting is None or meeting.summary is None:
                return False
            meeting.summary = None
            return True

    def update_state(self, meeting_id: str, state: MeetingState) -> bool:
        """Atomically update meeting state.

        Args:
            meeting_id: Meeting ID.
            state: New state.

        Returns:
            True if updated, False if meeting not found.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            if meeting is None:
                return False
            meeting.state = state
            return True

    def update_title(self, meeting_id: str, title: str) -> bool:
        """Atomically update meeting title.

        Args:
            meeting_id: Meeting ID.
            title: New title.

        Returns:
            True if updated, False if meeting not found.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            if meeting is None:
                return False
            meeting.title = title
            return True

    def update_end_time(self, meeting_id: str, end_time: datetime) -> bool:
        """Atomically update meeting end time.

        Args:
            meeting_id: Meeting ID.
            end_time: New end time.

        Returns:
            True if updated, False if meeting not found.
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            if meeting is None:
                return False
            meeting.ended_at = end_time
            return True

    def compute_next_segment_id(self, meeting_id: str) -> int:
        """Compute next segment ID for in-memory meeting.

        Args:
            meeting_id: Meeting ID.

        Returns:
            Next segment ID (0 if meeting or segments missing).
        """
        with self._lock:
            meeting = self._meetings.get(meeting_id)
            if meeting is None or not meeting.segments:
                return 0
            return max(s.segment_id for s in meeting.segments) + 1

    def delete(self, meeting_id: str) -> bool:
        """Delete a meeting.

        Args:
            meeting_id: Meeting ID.

        Returns:
            True if deleted, False if not found.
        """
        with self._lock:
            if meeting_id in self._meetings:
                del self._meetings[meeting_id]
                return True
            return False

    @property
    def active_count(self) -> int:
        """Count of meetings in RECORDING or STOPPING state."""
        with self._lock:
            return sum(
                m.state in (MeetingState.RECORDING, MeetingState.STOPPING)
                for m in self._meetings.values()
            )

    @property
    def integrations(self) -> InMemoryIntegrationRepository:
        """Shared integration repository for memory-backed persistence."""
        return self._integrations
