"""Export application service.

Orchestrates transcript export to various formats.
"""

from __future__ import annotations

from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Protocol, Self

from noteflow.config.constants import (
    ERROR_MSG_MEETING_PREFIX,
    EXPORT_EXT_HTML,
    EXPORT_EXT_PDF,
)
from noteflow.infrastructure.export import (
    HtmlExporter,
    MarkdownExporter,
    PdfExporter,
    TranscriptExporter,
)
from noteflow.infrastructure.logging import get_logger

if TYPE_CHECKING:
    from noteflow.domain.entities import Meeting, Segment
    from noteflow.domain.ports.repositories import MeetingRepository, SegmentRepository
    from noteflow.domain.value_objects import MeetingId


class ExportRepositoryProvider(Protocol):
    """Minimal repository provider for export operations."""

    @property
    def meetings(self) -> MeetingRepository: ...

    @property
    def segments(self) -> SegmentRepository: ...

    async def __aenter__(self) -> Self: ...

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: object,
    ) -> None: ...

logger = get_logger(__name__)


class ExportFormat(Enum):
    """Supported export formats."""

    MARKDOWN = "markdown"
    HTML = "html"
    PDF = "pdf"


class ExportService:
    """Application service for transcript export operations.

    Provides use cases for exporting meeting transcripts to various formats.
    """

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

        Args:
            uow: Unit of work for persistence.
        """
        self._uow = uow
        self._exporters: dict[ExportFormat, TranscriptExporter] = {
            ExportFormat.MARKDOWN: MarkdownExporter(),
            ExportFormat.HTML: HtmlExporter(),
            ExportFormat.PDF: PdfExporter(),
        }

    def get_exporter(self, fmt: ExportFormat) -> TranscriptExporter:
        """Get exporter for format.

        Args:
            fmt: Export format.

        Returns:
            Exporter instance.

        Raises:
            ValueError: If format is not supported.
        """
        exporter = self._exporters.get(fmt)
        if exporter is None:
            raise ValueError(f"Unsupported export format: {fmt}")
        return exporter

    async def export_transcript(
        self,
        meeting_id: MeetingId,
        fmt: ExportFormat = ExportFormat.MARKDOWN,
    ) -> str | bytes:
        """Export meeting transcript.

        Args:
            meeting_id: Meeting identifier.
            fmt: Export format.

        Returns:
            Formatted transcript as string (text formats) or bytes (binary formats like PDF).

        Raises:
            ValueError: If meeting not found.
        """
        logger.info(
            "Starting transcript export",
            meeting_id=str(meeting_id),
            format=fmt.value,
        )
        async with self._uow:
            found_meeting = await self._uow.meetings.get(meeting_id)
            if not found_meeting:
                msg = f"{ERROR_MSG_MEETING_PREFIX}{meeting_id} not found"
                logger.warning(
                    "Export failed: meeting not found",
                    meeting_id=str(meeting_id),
                )
                raise ValueError(msg)

            segments = await self._uow.segments.get_by_meeting(meeting_id)
            segment_count = len(segments)
            logger.debug(
                "Retrieved segments for export",
                meeting_id=str(meeting_id),
                segment_count=segment_count,
            )

            exporter = self.get_exporter(fmt)
            result = exporter.export(found_meeting, segments)

            content_size = len(result) if isinstance(result, bytes) else len(result.encode("utf-8"))
            logger.info(
                "Transcript export completed",
                meeting_id=str(meeting_id),
                format=fmt.value,
                segment_count=segment_count,
                content_size_bytes=content_size,
            )
            return result

    async def export_to_file(
        self,
        meeting_id: MeetingId,
        output_path: Path,
        fmt: ExportFormat | None = None,
    ) -> Path:
        """Export meeting transcript to file.

        Args:
            meeting_id: Meeting identifier.
            output_path: Output file path (extension determines format if not specified).
            fmt: Export format (optional, inferred from extension if not provided).

        Returns:
            Path to the exported file.

        Raises:
            ValueError: If meeting not found or format cannot be determined.
        """
        logger.info(
            "Starting file export",
            meeting_id=str(meeting_id),
            output_path=str(output_path),
            format=fmt.value if fmt else "inferred",
        )

        # Determine format from extension if not provided
        if fmt is None:
            fmt = self.infer_format_from_extension(output_path.suffix)
            logger.debug(
                "Format inferred from extension",
                extension=output_path.suffix,
                inferred_format=fmt.value,
            )

        content = await self.export_transcript(meeting_id, fmt)

        # Ensure correct extension
        exporter = self.get_exporter(fmt)
        original_path = output_path
        if output_path.suffix != exporter.file_extension:
            output_path = output_path.with_suffix(exporter.file_extension)
            logger.debug(
                "Adjusted file extension",
                original_path=str(original_path),
                adjusted_path=str(output_path),
                expected_extension=exporter.file_extension,
            )

        output_path.parent.mkdir(parents=True, exist_ok=True)
        try:
            if isinstance(content, bytes):
                output_path.write_bytes(content)
            else:
                output_path.write_text(content, encoding="utf-8")

            file_size = output_path.stat().st_size
            logger.info(
                "File export completed",
                meeting_id=str(meeting_id),
                output_path=str(output_path),
                format=fmt.value,
                file_size_bytes=file_size,
            )
        except OSError as exc:
            logger.error(
                "File write failed",
                meeting_id=str(meeting_id),
                output_path=str(output_path),
                error=str(exc),
            )
            raise

        return output_path

    def infer_format_from_extension(self, extension: str) -> ExportFormat:
        """Infer export format from file extension.

        Args:
            extension: File extension (e.g., '.md', '.html').

        Returns:
            Inferred export format.

        Raises:
            ValueError: If extension is not recognized.
        """
        extension_map = {
            ".md": ExportFormat.MARKDOWN,
            ".markdown": ExportFormat.MARKDOWN,
            EXPORT_EXT_HTML: ExportFormat.HTML,
            ".htm": ExportFormat.HTML,
            EXPORT_EXT_PDF: ExportFormat.PDF,
        }
        normalized_ext = extension.lower()
        fmt = extension_map.get(normalized_ext)
        if fmt is None:
            logger.warning(
                "Unrecognized file extension for format inference",
                extension=extension,
                supported_extensions=list(extension_map.keys()),
            )
            raise ValueError(
                f"Cannot infer format from extension '{extension}'. "
                f"Supported: {', '.join(extension_map.keys())}"
            )
        logger.debug(
            "Format inference successful",
            extension=normalized_ext,
            inferred_format=fmt.value,
        )
        return fmt

    def get_supported_formats(self) -> list[tuple[str, str]]:
        """Get list of supported export formats.

        Returns:
            List of (format_name, file_extension) tuples.
        """
        return [(e.format_name, e.file_extension) for e in self._exporters.values()]

    async def preview_export(
        self,
        meeting: Meeting,
        segments: list[Segment],
        fmt: ExportFormat = ExportFormat.MARKDOWN,
    ) -> str | bytes:
        """Preview export without fetching from database.

        Useful for previewing exports with in-memory data.

        Args:
            meeting: Meeting entity.
            segments: List of segments.
            fmt: Export format.

        Returns:
            Formatted transcript as string (text formats) or bytes (binary formats like PDF).
        """
        exporter = self.get_exporter(fmt)
        return exporter.export(meeting, segments)
