"""Speaker assignment and renaming operations."""

from __future__ import annotations

from typing import TYPE_CHECKING

from noteflow.domain.entities import Segment
from noteflow.infrastructure.diarization import SpeakerTurn, assign_speaker

from ...proto import noteflow_pb2
from ..converters import parse_meeting_id_or_abort, parse_meeting_id_or_none
from ..errors import abort_invalid_argument
from ._types import GrpcContext

if TYPE_CHECKING:
    from collections.abc import Sequence

    from ..protocols import ServicerHost


def apply_speaker_to_segment(
    segment: Segment,
    turns: Sequence[SpeakerTurn],
) -> bool:
    """Assign speaker to segment from diarization turns.

    Consolidates the 3 duplicated speaker assignment patterns.

    Args:
        segment: Domain segment to update.
        turns: Sequence of speaker turns from diarization.

    Returns:
        True if speaker was assigned, False otherwise.
    """
    speaker_id, confidence = assign_speaker(
        segment.start_time,
        segment.end_time,
        turns,
    )
    if speaker_id is None:
        return False
    segment.speaker_id = speaker_id
    segment.speaker_confidence = confidence
    return True


class SpeakerMixin:
    """Mixin providing speaker assignment and renaming functionality."""

    def maybe_assign_speaker(
        self: ServicerHost,
        meeting_id: str,
        segment: Segment,
    ) -> None:
        """Assign speaker to a segment using streaming diarization turns (best-effort)."""
        if self.diarization_engine is None:
            return
        if meeting_id in self.diarization_streaming_failed:
            return
        if turns := self.diarization_turns.get(meeting_id):
            apply_speaker_to_segment(segment, turns)
        else:
            return

    async def collect_speaker_ids(self: ServicerHost, meeting_id: str) -> list[str]:
        """Collect distinct speaker IDs for a meeting."""
        parsed_meeting_id = parse_meeting_id_or_none(meeting_id)
        if parsed_meeting_id is None:
            return []

        async with self.create_repository_provider() as repo:
            segments = await repo.segments.get_by_meeting(parsed_meeting_id)
            return sorted({s.speaker_id for s in segments if s.speaker_id})

    async def RenameSpeaker(
        self: ServicerHost,
        request: noteflow_pb2.RenameSpeakerRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.RenameSpeakerResponse:
        """Rename a speaker ID in all segments of a meeting.

        Update all segments where speaker_id matches old_speaker_id
        to use new_speaker_name instead.
        """
        if not request.old_speaker_id or not request.new_speaker_name:
            await abort_invalid_argument(
                context, "old_speaker_id and new_speaker_name are required"
            )

        meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)

        updated_count = 0

        async with self.create_repository_provider() as repo:
            segments = await repo.segments.get_by_meeting(meeting_id)

            for segment in segments:
                if segment.speaker_id == request.old_speaker_id:
                    # For DB segments with db_id, use update_speaker
                    if segment.db_id is not None:
                        await repo.segments.update_speaker(
                            segment.db_id,
                            request.new_speaker_name,
                            segment.speaker_confidence,
                        )
                    else:
                        # Memory segments: update directly
                        segment.speaker_id = request.new_speaker_name
                    updated_count += 1

            await repo.commit()

        return noteflow_pb2.RenameSpeakerResponse(
            segments_updated=updated_count,
            success=updated_count > 0,
        )
