"""
Optimistic event publishing - attempts immediate broker publish after commit.

Reduces latency from ~100ms (polling) to <10ms (immediate) in happy path.
"""

import asyncio
import logging
from typing import Any

# Optional prometheus metrics
try:
    from prometheus_client import Counter, Histogram

    PROMETHEUS_AVAILABLE = True
except ImportError:
    PROMETHEUS_AVAILABLE = False
    # No-op fallbacks
    from contextlib import contextmanager

    class _NoOpMetric:
        def inc(self, *args: Any, **kwargs: Any) -> None:
            pass

        def observe(self, *args: Any, **kwargs: Any) -> None:
            pass

        def labels(self, *args: Any, **kwargs: Any) -> "_NoOpMetric":
            return self

        @contextmanager
        def time(self):
            """No-op context manager for timing."""
            yield

    def _noop_counter(*args: Any, **kwargs: Any) -> _NoOpMetric:
        return _NoOpMetric()

    def _noop_histogram(*args: Any, **kwargs: Any) -> _NoOpMetric:
        return _NoOpMetric()

    Counter = _noop_counter  # type: ignore[misc,assignment]
    Histogram = _noop_histogram  # type: ignore[misc,assignment]


from .types import OutboxEvent

logger = logging.getLogger(__name__)

# Metrics (no-op if prometheus not installed)
OPTIMISTIC_SEND_ATTEMPTS = Counter(
    "outbox_optimistic_send_attempts_total", "Total optimistic send attempts"
)
OPTIMISTIC_SEND_SUCCESS = Counter(
    "outbox_optimistic_send_success_total", "Successful optimistic sends"
)
OPTIMISTIC_SEND_FAILURES = Counter(
    "outbox_optimistic_send_failures_total",
    "Failed optimistic sends (will fallback to polling)",
    ["reason"],
)
OPTIMISTIC_SEND_LATENCY = Histogram(
    "outbox_optimistic_send_latency_seconds",
    "Latency of optimistic send operation",
    buckets=(0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0),
)


class OptimisticPublisher:
    """
    Publishes events immediately after transaction commit.

    Benefits:
    - 10x faster than polling (< 10ms vs ~100ms)
    - Still maintains exactly-once semantics
    - Failures fallback to polling worker (safety net)
    """

    def __init__(
        self,
        storage,  # OutboxStorage
        broker,  # MessageBroker
        enabled: bool = True,
        timeout_seconds: float = 0.5,
    ):
        self.storage = storage
        self.broker = broker
        self.enabled = enabled
        self.timeout_seconds = timeout_seconds

    async def publish_after_commit(self, event: OutboxEvent) -> bool:
        """
        Attempt immediate publish after transaction commits.

        CRITICAL: Call AFTER transaction commit, not before!

        Returns:
            True if published successfully, False if failed (will fallback)
        """
        if not self.enabled:
            return False

        OPTIMISTIC_SEND_ATTEMPTS.inc()

        try:
            with OPTIMISTIC_SEND_LATENCY.time():
                await asyncio.wait_for(
                    self.broker.publish(
                        topic=self._resolve_topic(event),
                        key=event.partition_key,
                        value=event.payload,
                        headers=event.headers,
                    ),
                    timeout=self.timeout_seconds,
                )

            # Success!
            await self.storage.mark_sent(event.event_id)
            OPTIMISTIC_SEND_SUCCESS.inc()

            logger.info(f"Optimistic send succeeded: {event.event_id}")
            return True

        except TimeoutError:
            OPTIMISTIC_SEND_FAILURES.labels(reason="timeout").inc()
            logger.warning(f"Optimistic timeout: {event.event_id}, will fallback")
            return False

        except Exception as exc:
            OPTIMISTIC_SEND_FAILURES.labels(reason=type(exc).__name__).inc()
            logger.warning(f"Optimistic failed: {event.event_id}, will fallback: {exc}")
            return False

    def _resolve_topic(self, event: OutboxEvent) -> str:
        """Map event to broker topic."""
        if event.routing_key:
            return event.routing_key
        return f"events.{event.event_type.lower().replace('_', '.')}"
