"""Observability ports for application services.

Defines protocols for usage event emission that can be implemented
by different observability backends (OpenTelemetry, metrics systems, etc.).
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import Protocol

from noteflow.domain.utils.time import utc_now


@dataclass(frozen=True, slots=True)
class UsageMetrics:
    """Metrics for usage event tracking.

    Groups provider, model, and performance metrics together
    to reduce parameter count in convenience methods.
    """

    provider_name: str | None = None
    """Provider name (e.g., 'openai', 'anthropic', 'ollama')."""

    model_name: str | None = None
    """Model name (e.g., 'gpt-4', 'claude-3-opus')."""

    tokens_input: int | None = None
    """Input tokens consumed."""

    tokens_output: int | None = None
    """Output tokens generated."""

    latency_ms: float | None = None
    """Operation latency in milliseconds."""


@dataclass(frozen=True, slots=True)
class UsageEvent:
    """Usage event for analytics and observability.

    Captures resource usage for billing, analytics, and monitoring.
    Events are immutable and timestamped at creation.
    """

    event_type: str
    """Event type identifier (e.g., 'summarization.completed', 'transcription.completed')."""

    timestamp: datetime = field(default_factory=utc_now)
    """When the event occurred."""

    # Resource identifiers
    meeting_id: str | None = None
    """Associated meeting ID, if applicable."""

    workspace_id: str | None = None
    """Associated workspace ID, if applicable."""

    project_id: str | None = None
    """Associated project ID, if applicable."""

    # Provider/model tracking
    provider_name: str | None = None
    """Provider name (e.g., 'openai', 'anthropic', 'ollama')."""

    model_name: str | None = None
    """Model name (e.g., 'gpt-4', 'claude-3-opus')."""

    # Usage metrics
    tokens_input: int | None = None
    """Input tokens consumed."""

    tokens_output: int | None = None
    """Output tokens generated."""

    latency_ms: float | None = None
    """Operation latency in milliseconds."""

    # Status
    success: bool = True
    """Whether the operation succeeded."""

    error_code: str | None = None
    """Error code if operation failed."""

    # Additional context
    attributes: dict[str, object] = field(default_factory=dict)
    """Additional key-value attributes for the event."""

    @classmethod
    def from_metrics(
        cls,
        event_type: str,
        metrics: UsageMetrics,
        *,
        meeting_id: str | None = None,
        success: bool = True,
        error_code: str | None = None,
        attributes: dict[str, object] | None = None,
    ) -> UsageEvent:
        """Create usage event from metrics object.

        Args:
            event_type: Event type identifier.
            metrics: Provider/model metrics.
            meeting_id: Associated meeting ID.
            success: Whether the operation succeeded.
            error_code: Error code if failed.
            attributes: Additional context attributes.

        Returns:
            New UsageEvent instance.
        """
        return cls(
            event_type=event_type,
            meeting_id=meeting_id,
            provider_name=metrics.provider_name,
            model_name=metrics.model_name,
            tokens_input=metrics.tokens_input,
            tokens_output=metrics.tokens_output,
            latency_ms=metrics.latency_ms,
            success=success,
            error_code=error_code,
            attributes=attributes or {},
        )


class UsageEventSink(Protocol):
    """Protocol for usage event emission.

    Implementations handle recording usage events to various backends
    (OpenTelemetry spans, metrics systems, databases, etc.).
    """

    def record(self, event: UsageEvent) -> None:
        """Record a usage event.

        Args:
            event: The usage event to record.
        """
        ...

    def record_simple(
        self,
        event_type: str,
        metrics: UsageMetrics | None = None,
        *,
        meeting_id: str | None = None,
        success: bool = True,
        error_code: str | None = None,
        **attributes: object,
    ) -> None:
        """Convenience method to record a usage event with common fields.

        Args:
            event_type: Event type identifier.
            metrics: Optional provider/model metrics.
            meeting_id: Associated meeting ID.
            success: Whether the operation succeeded.
            error_code: Error code if failed.
            **attributes: Additional context attributes.
        """
        ...


class NullUsageEventSink:
    """No-op usage event sink for when observability is disabled."""

    def record(self, event: UsageEvent) -> None:
        """Discard the event."""

    def record_simple(
        self,
        event_type: str,
        metrics: UsageMetrics | None = None,
        *,
        meeting_id: str | None = None,
        success: bool = True,
        error_code: str | None = None,
        **attributes: object,
    ) -> None:
        """Discard the event."""
