"""Meeting application service.

Orchestrates meeting-related use cases with persistence.
"""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import TYPE_CHECKING

from noteflow.domain.entities import (
    ActionItem,
    Annotation,
    KeyPoint,
    Meeting,
    Segment,
    Summary,
    WordTiming,
)
from noteflow.domain.value_objects import AnnotationId, AnnotationType, MeetingId
from noteflow.infrastructure.logging import get_logger, log_state_transition

if TYPE_CHECKING:
    from collections.abc import Sequence as SequenceType

    from noteflow.domain.ports.unit_of_work import UnitOfWork
    from noteflow.domain.value_objects import MeetingState

logger = get_logger(__name__)


@dataclass(frozen=True, slots=True)
class SegmentData:
    """Data for creating a transcript segment.

    Groups segment parameters to reduce parameter count in service methods.
    """

    segment_id: int
    """Segment sequence number."""

    text: str
    """Transcript text."""

    start_time: float
    """Start time in seconds."""

    end_time: float
    """End time in seconds."""

    words: list[WordTiming] = field(default_factory=list)
    """Optional word-level timing."""

    language: str = "en"
    """Detected language code."""

    language_confidence: float = 0.0
    """Language detection confidence."""

    avg_logprob: float = 0.0
    """Average log probability."""

    no_speech_prob: float = 0.0
    """No-speech probability."""


class MeetingService:
    """Application service for meeting operations.

    Provides use cases for managing meetings, segments, and summaries.
    All methods are async and expect a UnitOfWork to be provided.
    """

    def __init__(self, uow: UnitOfWork) -> None:
        """Initialize the meeting service.

        Args:
            uow: Unit of work for persistence.
        """
        self._uow = uow

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

        Args:
            title: Meeting title.
            metadata: Optional metadata.

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

        async with self._uow:
            saved = await self._uow.meetings.create(meeting)
            await self._uow.commit()
            logger.info("Created meeting", meeting_id=str(saved.id), title=title, state=saved.state.value)
            return saved

    async def get_meeting(self, meeting_id: MeetingId) -> Meeting | None:
        """Get a meeting by ID.

        Args:
            meeting_id: Meeting identifier.

        Returns:
            Meeting if found, None otherwise.
        """
        async with self._uow:
            meeting = await self._uow.meetings.get(meeting_id)
            if meeting is None:
                logger.debug("Meeting not found", meeting_id=str(meeting_id))
            else:
                logger.debug("Retrieved meeting", meeting_id=str(meeting_id), state=meeting.state.value)
            return meeting

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

        Args:
            states: Filter to specific meeting states (None = all).
            limit: Maximum results to return.
            offset: Number of results to skip for pagination.
            sort_desc: If True, newest meetings first.

        Returns:
            Tuple of (meeting sequence, total matching count).
        """
        async with self._uow:
            meetings, total = await self._uow.meetings.list_all(
                states=states,
                limit=limit,
                offset=offset,
                sort_desc=sort_desc,
            )
            logger.debug("Listed meetings", count=len(meetings), total=total, limit=limit, offset=offset)
            return meetings, total

    async def start_recording(self, meeting_id: MeetingId) -> Meeting | None:
        """Start recording a meeting.

        Args:
            meeting_id: Meeting identifier.

        Returns:
            Updated meeting, or None if not found.
        """
        async with self._uow:
            meeting = await self._uow.meetings.get(meeting_id)
            if meeting is None:
                logger.warning("Cannot start recording: meeting not found", meeting_id=str(meeting_id))
                return None

            previous_state = meeting.state
            meeting.start_recording()
            await self._uow.meetings.update(meeting)
            await self._uow.commit()
            log_state_transition("meeting", str(meeting_id), previous_state, meeting.state)
            return meeting

    async def stop_meeting(self, meeting_id: MeetingId) -> Meeting | None:
        """Stop a meeting through graceful STOPPING state.

        Transitions: RECORDING -> STOPPING -> STOPPED

        Args:
            meeting_id: Meeting identifier.

        Returns:
            Updated meeting, or None if not found.
        """
        async with self._uow:
            meeting = await self._uow.meetings.get(meeting_id)
            if meeting is None:
                logger.warning("Cannot stop meeting: not found", meeting_id=str(meeting_id))
                return None

            previous_state = meeting.state
            meeting.begin_stopping()  # RECORDING -> STOPPING -> STOPPED
            meeting.stop_recording()
            await self._uow.meetings.update(meeting)
            await self._uow.commit()
            log_state_transition("meeting", str(meeting_id), previous_state, meeting.state)
            return meeting

    async def complete_meeting(self, meeting_id: MeetingId) -> Meeting | None:
        """Mark a meeting as completed.

        Args:
            meeting_id: Meeting identifier.

        Returns:
            Updated meeting, or None if not found.
        """
        async with self._uow:
            meeting = await self._uow.meetings.get(meeting_id)
            if meeting is None:
                logger.warning("Cannot complete meeting: not found", meeting_id=str(meeting_id))
                return None

            previous_state = meeting.state
            meeting.complete()
            await self._uow.meetings.update(meeting)
            await self._uow.commit()
            log_state_transition("meeting", str(meeting_id), previous_state, meeting.state)
            return meeting

    async def delete_meeting(self, meeting_id: MeetingId) -> bool:
        """Delete meeting with complete cleanup.

        Removes:
        1. Filesystem assets (via asset repository)
        2. Database records (cascade deletes children)

        Args:
            meeting_id: Meeting identifier.

        Returns:
            True if deleted, False if not found.
        """
        async with self._uow:
            meeting = await self._uow.meetings.get(meeting_id)
            if meeting is None:
                logger.warning("Cannot delete meeting: not found", meeting_id=str(meeting_id))
                return False

            await self._uow.assets.delete_meeting_assets(meeting_id, meeting.asset_path)
            success = await self._uow.meetings.delete(meeting_id)
            if success:
                await self._uow.commit()
                logger.info("Deleted meeting", meeting_id=str(meeting_id), title=meeting.title)

            return success

    async def add_segment(
        self,
        meeting_id: MeetingId,
        data: SegmentData,
    ) -> Segment:
        """Add a transcript segment to a meeting.

        Args:
            meeting_id: Meeting identifier.
            data: Segment data including text, timing, and metadata.

        Returns:
            Added segment.
        """
        segment = Segment(
            segment_id=data.segment_id,
            text=data.text,
            start_time=data.start_time,
            end_time=data.end_time,
            meeting_id=meeting_id,
            words=data.words,
            language=data.language,
            language_confidence=data.language_confidence,
            avg_logprob=data.avg_logprob,
            no_speech_prob=data.no_speech_prob,
        )

        async with self._uow:
            saved = await self._uow.segments.add(meeting_id, segment)
            await self._uow.commit()
            logger.debug(
                "Added segment",
                meeting_id=str(meeting_id),
                segment_id=data.segment_id,
                start=data.start_time,
                end=data.end_time,
            )
            return saved

    async def add_segments_batch(self, meeting_id: MeetingId, segments: Sequence[Segment]) -> Sequence[Segment]:
        """Add multiple segments in batch."""
        async with self._uow:
            saved = await self._uow.segments.add_batch(meeting_id, segments)
            await self._uow.commit()
            logger.debug("Added segments batch", meeting_id=str(meeting_id), count=len(segments))
            return saved

    async def get_segments(
        self,
        meeting_id: MeetingId,
        include_words: bool = True,
    ) -> Sequence[Segment]:
        """Get all segments for a meeting.

        Args:
            meeting_id: Meeting identifier.
            include_words: Include word-level timing.

        Returns:
            List of segments ordered by segment_id.
        """
        async with self._uow:
            return await self._uow.segments.get_by_meeting(
                meeting_id,
                include_words=include_words,
            )

    async def search_segments(
        self,
        query_embedding: list[float],
        limit: int = 10,
        meeting_id: MeetingId | None = None,
    ) -> Sequence[tuple[Segment, float]]:
        """Search segments by semantic similarity.

        Args:
            query_embedding: Query embedding vector.
            limit: Maximum number of results.
            meeting_id: Optional meeting to restrict search to.

        Returns:
            List of (segment, similarity_score) tuples.
        """
        async with self._uow:
            return await self._uow.segments.search_semantic(
                query_embedding=query_embedding,
                limit=limit,
                meeting_id=meeting_id,
            )

    async def save_summary(
        self,
        meeting_id: MeetingId,
        executive_summary: str,
        key_points: list[KeyPoint] | None = None,
        action_items: list[ActionItem] | None = None,
        provider_name: str = "",
        model_name: str = "",
    ) -> Summary:
        """Save or update a meeting summary.

        Args:
            meeting_id: Meeting identifier.
            executive_summary: Executive summary text.
            key_points: List of key points.
            action_items: List of action items.
            provider_name: Name of the provider that generated the summary.
            model_name: Name of the model that generated the summary.

        Returns:
            Saved summary.
        """
        summary = Summary(
            meeting_id=meeting_id,
            executive_summary=executive_summary,
            key_points=key_points or [],
            action_items=action_items or [],
            generated_at=datetime.now(UTC),
            provider_name=provider_name,
            model_name=model_name,
        )

        async with self._uow:
            saved = await self._uow.summaries.save(summary)
            await self._uow.commit()
            logger.info("Saved summary", meeting_id=str(meeting_id), provider=provider_name or "unknown", model=model_name or "unknown")
            return saved

    async def get_summary(self, meeting_id: MeetingId) -> Summary | None:
        """Get summary for a meeting."""
        async with self._uow:
            summary = await self._uow.summaries.get_by_meeting(meeting_id)
            if summary is None:
                logger.debug("Summary not found", meeting_id=str(meeting_id))
            else:
                logger.debug("Retrieved summary", meeting_id=str(meeting_id), provider=summary.provider_name or "unknown")
            return summary

    # Annotation methods

    async def add_annotation(
        self,
        meeting_id: MeetingId,
        annotation_type: AnnotationType,
        text: str,
        start_time: float,
        end_time: float,
        segment_ids: list[int] | None = None,
    ) -> Annotation:
        """Add an annotation to a meeting.

        Args:
            meeting_id: Meeting identifier.
            annotation_type: Type of annotation.
            text: Annotation text.
            start_time: Start time in seconds.
            end_time: End time in seconds.
            segment_ids: Optional list of linked segment IDs.

        Returns:
            Added annotation.
        """
        from uuid import uuid4

        annotation = Annotation(
            id=AnnotationId(uuid4()),
            meeting_id=meeting_id,
            annotation_type=annotation_type,
            text=text,
            start_time=start_time,
            end_time=end_time,
            segment_ids=segment_ids or [],
        )

        async with self._uow:
            saved = await self._uow.annotations.add(annotation)
            await self._uow.commit()
            logger.info(
                "Added annotation",
                meeting_id=str(meeting_id),
                annotation_id=str(annotation.id),
                annotation_type=annotation_type.value,
                start_time=start_time,
                end_time=end_time,
            )
            return saved

    async def get_annotation(self, annotation_id: AnnotationId) -> Annotation | None:
        """Get an annotation by ID.

        Args:
            annotation_id: Annotation identifier.

        Returns:
            Annotation if found, None otherwise.
        """
        async with self._uow:
            return await self._uow.annotations.get(annotation_id)

    async def get_annotations(
        self,
        meeting_id: MeetingId,
    ) -> SequenceType[Annotation]:
        """Get all annotations for a meeting.

        Args:
            meeting_id: Meeting identifier.

        Returns:
            List of annotations ordered by start_time.
        """
        async with self._uow:
            return await self._uow.annotations.get_by_meeting(meeting_id)

    async def get_annotations_in_range(
        self,
        meeting_id: MeetingId,
        start_time: float,
        end_time: float,
    ) -> SequenceType[Annotation]:
        """Get annotations within a time range.

        Args:
            meeting_id: Meeting identifier.
            start_time: Start of time range in seconds.
            end_time: End of time range in seconds.

        Returns:
            List of annotations overlapping the time range.
        """
        async with self._uow:
            return await self._uow.annotations.get_by_time_range(meeting_id, start_time, end_time)

    async def update_annotation(self, annotation: Annotation) -> Annotation:
        """Update an existing annotation.

        Args:
            annotation: Annotation with updated fields.

        Returns:
            Updated annotation.

        Raises:
            ValueError: If annotation does not exist.
        """
        async with self._uow:
            updated = await self._uow.annotations.update(annotation)
            await self._uow.commit()
            logger.info(
                "Updated annotation",
                annotation_id=str(annotation.id),
                meeting_id=str(annotation.meeting_id),
                annotation_type=annotation.annotation_type.value,
            )
            return updated

    async def delete_annotation(self, annotation_id: AnnotationId) -> bool:
        """Delete an annotation.

        Args:
            annotation_id: Annotation identifier.

        Returns:
            True if deleted, False if not found.
        """
        async with self._uow:
            success = await self._uow.annotations.delete(annotation_id)
            if success:
                await self._uow.commit()
                logger.info("Deleted annotation", annotation_id=str(annotation_id))
            else:
                logger.warning(
                    "Cannot delete annotation: not found",
                    annotation_id=str(annotation_id),
                )
            return success
