# edited by glg
from typing import Any, Dict, Iterable, List

from pypos.modules.sinkronisasi.services.event_ingestion_backpressure_service import (
    EventIngestionBackpressureService,
)
from pypos.modules.sinkronisasi.services.event_outbox_service import EventOutboxService


class EventIngestionGatewayError(RuntimeError):
    pass


class EventIngestionGatewayService:
    """
    Gateway event-first ingestion:
    - menerima event payload dari producer
    - evaluasi backpressure
    - enqueue ke outbox dengan dedup deterministik
    """

    def __init__(self, outbox_service=None, backpressure_service=None):
        self.outbox_service = outbox_service or EventOutboxService()
        self.backpressure_service = backpressure_service or EventIngestionBackpressureService()

    @staticmethod
    def _normalize_topic(value: Any) -> str:
        return str(value or "").strip()

    @staticmethod
    def _normalize_source_id(value: Any) -> str:
        return str(value or "").strip()

    @staticmethod
    def _normalize_headers(value: Any) -> Dict[str, Any]:
        if isinstance(value, dict):
            return dict(value)
        return {}

    @staticmethod
    def _ensure_event_list(events: Any) -> List[Dict[str, Any]]:
        if isinstance(events, list):
            out = []
            for item in events:
                if isinstance(item, dict):
                    out.append(dict(item))
                else:
                    out.append({"value": item})
            return out
        if events is None:
            return []
        if isinstance(events, tuple):
            return EventIngestionGatewayService._ensure_event_list(list(events))
        raise EventIngestionGatewayError("Payload events harus berupa list.")

    def _build_runtime_metrics(self, *, metrics: Dict[str, Any]) -> Dict[str, Any]:
        queue_metrics = self.outbox_service.get_queue_metrics() if self.outbox_service else {}
        raw = metrics if isinstance(metrics, dict) else {}
        return {
            "pending": int(raw.get("pending", queue_metrics.get("pending", 0)) or 0),
            "inflight": int(raw.get("inflight", queue_metrics.get("inflight", 0)) or 0),
            "error_rate_pct": float(raw.get("error_rate_pct", 0.0) or 0.0),
            "avg_latency_ms": float(raw.get("avg_latency_ms", 0.0) or 0.0),
        }

    def ingest_events(
        self,
        *,
        topic: str,
        events: Iterable[Dict[str, Any]],
        source_id: str = "",
        headers: Dict[str, Any] = None,
        metrics: Dict[str, Any] = None,
    ) -> Dict[str, Any]:
        normalized_topic = self._normalize_topic(topic)
        if not normalized_topic:
            raise EventIngestionGatewayError("topic wajib diisi.")

        normalized_events = self._ensure_event_list(events)
        normalized_source_id = self._normalize_source_id(source_id)
        normalized_headers = self._normalize_headers(headers)

        runtime_metrics = self._build_runtime_metrics(metrics=metrics or {})
        gate = self.backpressure_service.evaluate(runtime_metrics)
        ingest_enabled = bool(gate.get("ingest_enabled"))
        allowed_batch = max(1, int(gate.get("recommended_batch_size") or 1))

        payload_total = int(len(normalized_events))
        if not ingest_enabled:
            return {
                "accepted": False,
                "reason": str(gate.get("reason") or "ingest_blocked"),
                "mode": str(gate.get("mode") or "critical"),
                "ingested_count": 0,
                "duplicate_count": 0,
                "invalid_count": 0,
                "dropped_count": payload_total,
                "total_events": payload_total,
                "recommended_batch_size": allowed_batch,
            }

        selected_events = normalized_events[:allowed_batch]
        inserted = 0
        duplicate = 0
        invalid = 0
        accepted_event_ids: List[str] = []
        dedup_keys: List[str] = []
        for payload in selected_events:
            item = payload if isinstance(payload, dict) else {"value": payload}
            dedup_key = str(item.get("dedup_key") or "").strip()
            if dedup_key:
                item = dict(item)
                item.pop("dedup_key", None)
            try:
                enqueue_result = self.outbox_service.enqueue_event(
                    topic=normalized_topic,
                    payload=item,
                    source_id=normalized_source_id,
                    dedup_key=dedup_key,
                    headers=normalized_headers,
                )
            except (RuntimeError, TypeError, ValueError):
                invalid += 1
                continue
            if bool(enqueue_result.get("inserted")):
                inserted += 1
            else:
                duplicate += 1
            accepted_event_ids.append(str(enqueue_result.get("event_id") or ""))
            dedup_keys.append(str(enqueue_result.get("dedup_key") or ""))

        dropped = max(0, payload_total - len(selected_events))
        return {
            "accepted": True,
            "reason": str(gate.get("reason") or "healthy"),
            "mode": str(gate.get("mode") or "normal"),
            "ingested_count": int(inserted),
            "duplicate_count": int(duplicate),
            "invalid_count": int(invalid),
            "dropped_count": int(dropped),
            "total_events": payload_total,
            "recommended_batch_size": allowed_batch,
            "event_ids": accepted_event_ids,
            "dedup_keys": dedup_keys,
        }

    def ingest_event_envelope(self, envelope: Dict[str, Any]) -> Dict[str, Any]:
        payload = envelope if isinstance(envelope, dict) else {}
        topic = self._normalize_topic(payload.get("topic"))
        events = self._ensure_event_list(payload.get("events"))
        source_id = self._normalize_source_id(payload.get("source_id"))
        headers = self._normalize_headers(payload.get("headers"))
        metrics_raw = payload.get("metrics")
        metrics = dict(metrics_raw) if isinstance(metrics_raw, dict) else {}
        return self.ingest_events(
            topic=topic,
            events=events,
            source_id=source_id,
            headers=headers,
            metrics=metrics,
        )
