"""Core domain entity converters for gRPC service."""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Protocol, cast

from noteflow.application.services.export_service import ExportFormat as ApplicationExportFormat
from noteflow.domain.entities import Annotation, Meeting, Segment, Summary, WordTiming
from noteflow.domain.value_objects import AnnotationType, MeetingId
from noteflow.domain.value_objects import ExportFormat as DomainExportFormat
from noteflow.infrastructure.converters import AsrConverter

from ...proto import noteflow_pb2

if TYPE_CHECKING:
    from noteflow.infrastructure.asr.dto import AsrResult


def word_to_proto(word: WordTiming) -> noteflow_pb2.WordTiming:
    """Convert domain WordTiming to protobuf.

    Consolidates the repeated WordTiming construction pattern.

    Args:
        word: Domain WordTiming entity.

    Returns:
        Protobuf WordTiming message.
    """
    return noteflow_pb2.WordTiming(
        word=word.word,
        start_time=word.start_time,
        end_time=word.end_time,
        probability=word.probability,
    )


def segment_to_final_segment_proto(segment: Segment) -> noteflow_pb2.FinalSegment:
    """Convert domain Segment to FinalSegment protobuf.

    Consolidates the repeated FinalSegment construction pattern.

    Args:
        segment: Domain Segment entity.

    Returns:
        Protobuf FinalSegment message.
    """
    words = [word_to_proto(w) for w in segment.words]
    return noteflow_pb2.FinalSegment(
        segment_id=segment.segment_id,
        text=segment.text,
        start_time=segment.start_time,
        end_time=segment.end_time,
        words=words,
        language=segment.language,
        language_confidence=segment.language_confidence,
        avg_logprob=segment.avg_logprob,
        no_speech_prob=segment.no_speech_prob,
        speaker_id=segment.speaker_id or "",
        speaker_confidence=segment.speaker_confidence,
    )


def summary_to_proto(summary: Summary) -> noteflow_pb2.Summary:
    """Convert domain Summary to protobuf."""
    key_points = [
        noteflow_pb2.KeyPoint(
            text=kp.text,
            segment_ids=kp.segment_ids,
            start_time=kp.start_time,
            end_time=kp.end_time,
        )
        for kp in summary.key_points
    ]
    action_items = [
        noteflow_pb2.ActionItem(
            text=ai.text,
            assignee=ai.assignee,
            due_date=ai.due_date.timestamp() if ai.due_date is not None else 0,
            priority=ai.priority if ai.priority > 0 else noteflow_pb2.PRIORITY_UNSPECIFIED,
            segment_ids=ai.segment_ids,
        )
        for ai in summary.action_items
    ]
    return noteflow_pb2.Summary(
        meeting_id=str(summary.meeting_id),
        executive_summary=summary.executive_summary,
        key_points=key_points,
        action_items=action_items,
        generated_at=(summary.generated_at.timestamp() if summary.generated_at is not None else 0),
        model_version=summary.model_version,
    )


def meeting_to_proto(
    meeting: Meeting,
    include_segments: bool = True,
    include_summary: bool = True,
) -> noteflow_pb2.Meeting:
    """Convert domain Meeting to protobuf."""
    segments = []
    if include_segments:
        segments = [segment_to_final_segment_proto(seg) for seg in meeting.segments]

    summary = None
    if include_summary and meeting.summary:
        summary = summary_to_proto(meeting.summary)

    proto = noteflow_pb2.Meeting(
        id=str(meeting.id),
        title=meeting.title,
        state=meeting.state.value,
        created_at=meeting.created_at.timestamp(),
        started_at=meeting.started_at.timestamp() if meeting.started_at else 0,
        ended_at=meeting.ended_at.timestamp() if meeting.ended_at else 0,
        duration_seconds=meeting.duration_seconds,
        segments=segments,
        summary=summary,
        metadata=meeting.metadata,
    )
    if meeting.project_id is not None:
        proto.project_id = str(meeting.project_id)
    return proto


def segment_to_proto_update(
    meeting_id: str,
    segment: Segment,
) -> noteflow_pb2.TranscriptUpdate:
    """Convert domain Segment to protobuf TranscriptUpdate."""
    return noteflow_pb2.TranscriptUpdate(
        meeting_id=meeting_id,
        update_type=noteflow_pb2.UPDATE_TYPE_FINAL,
        segment=segment_to_final_segment_proto(segment),
        server_timestamp=time.time(),
    )


def annotation_to_proto(annotation: Annotation) -> noteflow_pb2.Annotation:
    """Convert domain Annotation to protobuf."""
    return noteflow_pb2.Annotation(
        id=str(annotation.id),
        meeting_id=str(annotation.meeting_id),
        annotation_type=annotation_type_to_proto(annotation.annotation_type),
        text=annotation.text,
        start_time=annotation.start_time,
        end_time=annotation.end_time,
        segment_ids=annotation.segment_ids,
        created_at=annotation.created_at.timestamp(),
    )


def annotation_type_to_proto(
    annotation_type: AnnotationType,
) -> int:
    """Convert domain AnnotationType to protobuf enum."""
    mapping: dict[AnnotationType, int] = {
        AnnotationType.ACTION_ITEM: noteflow_pb2.ANNOTATION_TYPE_ACTION_ITEM,
        AnnotationType.DECISION: noteflow_pb2.ANNOTATION_TYPE_DECISION,
        AnnotationType.NOTE: noteflow_pb2.ANNOTATION_TYPE_NOTE,
        AnnotationType.RISK: noteflow_pb2.ANNOTATION_TYPE_RISK,
    }
    return mapping.get(annotation_type, noteflow_pb2.ANNOTATION_TYPE_UNSPECIFIED)


def proto_to_annotation_type(proto_type: int) -> AnnotationType:
    """Convert protobuf enum to domain AnnotationType."""
    mapping: dict[int, AnnotationType] = {
        int(noteflow_pb2.ANNOTATION_TYPE_ACTION_ITEM): AnnotationType.ACTION_ITEM,
        int(noteflow_pb2.ANNOTATION_TYPE_DECISION): AnnotationType.DECISION,
        int(noteflow_pb2.ANNOTATION_TYPE_NOTE): AnnotationType.NOTE,
        int(noteflow_pb2.ANNOTATION_TYPE_RISK): AnnotationType.RISK,
    }
    return mapping.get(proto_type, AnnotationType.NOTE)


def create_vad_update(
    meeting_id: str,
    update_type: noteflow_pb2.UpdateType,
) -> noteflow_pb2.TranscriptUpdate:
    """Create a VAD event update.

    Args:
        meeting_id: Meeting identifier.
        update_type: VAD_START or VAD_END.

    Returns:
        TranscriptUpdate with VAD event.
    """
    return noteflow_pb2.TranscriptUpdate(
        meeting_id=meeting_id,
        update_type=update_type,
        server_timestamp=time.time(),
    )


def create_congestion_info(
    processing_delay_ms: int,
    queue_depth: int,
    throttle_recommended: bool,
) -> noteflow_pb2.CongestionInfo:
    """Create congestion info for backpressure signaling.

    Args:
        processing_delay_ms: Time from chunk receipt to transcription (milliseconds).
        queue_depth: Number of chunks waiting to be processed.
        throttle_recommended: Signal that client should reduce sending rate.

    Returns:
        CongestionInfo protobuf message.
    """
    return noteflow_pb2.CongestionInfo(
        processing_delay_ms=processing_delay_ms,
        queue_depth=queue_depth,
        throttle_recommended=throttle_recommended,
    )


def create_ack_update(
    meeting_id: str,
    ack_sequence: int,
    congestion: noteflow_pb2.CongestionInfo | None = None,
) -> noteflow_pb2.TranscriptUpdate:
    """Create an acknowledgment update for received audio chunks.

    Args:
        meeting_id: Meeting identifier.
        ack_sequence: Highest contiguous chunk sequence received.
        congestion: Optional congestion info for backpressure signaling.

    Returns:
        TranscriptUpdate with ack_sequence set (update_type is UNSPECIFIED).
    """
    update = noteflow_pb2.TranscriptUpdate(
        meeting_id=meeting_id,
        update_type=noteflow_pb2.UPDATE_TYPE_UNSPECIFIED,
        server_timestamp=time.time(),
        ack_sequence=ack_sequence,
    )
    if congestion is not None:
        congestion_field = cast(_Copyable, update.congestion)
        congestion_field.CopyFrom(cast(_Copyable, congestion))
    return update


def create_segment_from_asr(
    meeting_id: MeetingId,
    segment_id: int,
    result: AsrResult,
    segment_start_time: float,
) -> Segment:
    """Create a Segment from ASR result.

    Use converters to transform ASR DTO to domain entities.
    """
    words = AsrConverter.result_to_domain_words(result)
    if segment_start_time:
        for word in words:
            word.start_time += segment_start_time
            word.end_time += segment_start_time

    return Segment(
        segment_id=segment_id,
        text=result.text,
        start_time=result.start + segment_start_time,
        end_time=result.end + segment_start_time,
        meeting_id=meeting_id,
        words=words,
        language=result.language,
        language_confidence=result.language_probability,
        avg_logprob=result.avg_logprob,
        no_speech_prob=result.no_speech_prob,
    )


def proto_to_export_format(proto_format: int) -> ApplicationExportFormat:
    """Convert protobuf ExportFormat to application ExportFormat."""
    if proto_format == noteflow_pb2.EXPORT_FORMAT_HTML:
        return ApplicationExportFormat.HTML
    if proto_format == noteflow_pb2.EXPORT_FORMAT_PDF:
        return ApplicationExportFormat.PDF
    return ApplicationExportFormat.MARKDOWN  # Default to Markdown


def export_format_to_proto(fmt: DomainExportFormat | ApplicationExportFormat) -> int:
    """Convert domain or application ExportFormat to proto enum."""
    # Both enums have the same values, so we can use either
    format_value = fmt.value if hasattr(fmt, "value") else str(fmt)
    mapping: dict[str, int] = {
        "markdown": noteflow_pb2.EXPORT_FORMAT_MARKDOWN,
        "html": noteflow_pb2.EXPORT_FORMAT_HTML,
        "pdf": noteflow_pb2.EXPORT_FORMAT_PDF,
    }
    return mapping.get(format_value, noteflow_pb2.EXPORT_FORMAT_UNSPECIFIED)


class _Copyable(Protocol):
    def CopyFrom(self, other: _Copyable) -> None: ...
