# edited by glg
import copy
import threading
import time
from datetime import datetime


class AsyncStaleSafeResultService:
    """
    Cache payload terakhir yang valid, lalu sediakan fallback stale-safe + guard retry schedule.
    Tujuan:
    - menghindari fallback query sinkron berat saat worker queue penuh
    - menjaga UI tetap responsif dengan payload stale yang aman
    - menjadwalkan retry async sekali per key agar tidak loop berlebihan.
    """

    def __init__(self, log_info=None, log_warning=None):
        self._cache = {}
        self._scheduled_retry_keys = set()
        self._lock = threading.Lock()
        self._log_info = log_info if callable(log_info) else (lambda *_args, **_kwargs: None)
        self._log_warning = log_warning if callable(log_warning) else (lambda *_args, **_kwargs: None)

    @staticmethod
    def _normalize_key(cache_key):
        return str(cache_key or "").strip()

    @staticmethod
    def _utc_timestamp_text():
        return datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")

    def remember_payload(self, cache_key, payload):
        key = self._normalize_key(cache_key)
        if not key:
            return False
        with self._lock:
            self._cache[key] = {
                "payload": copy.deepcopy(payload),
                "saved_at": float(time.time()),
            }
        return True

    def get_stale_payload(self, cache_key):
        key = self._normalize_key(cache_key)
        if not key:
            return None
        with self._lock:
            entry = self._cache.get(key)
            if not isinstance(entry, dict):
                return None
            payload = copy.deepcopy(entry.get("payload"))
        if isinstance(payload, dict):
            payload["stale"] = True
            payload.setdefault("reason", "queue_full_stale")
            payload.setdefault("reason_code", "QUEUE_FULL_STALE")
            payload.setdefault("stale_at", self._utc_timestamp_text())
        return payload

    def apply_stale_payload(
        self,
        *,
        cache_key,
        apply_callback,
        request_id=None,
        is_latest_request=None,
        mutate_payload=None,
        reason_code="QUEUE_FULL_STALE",
    ):
        payload = self.get_stale_payload(cache_key)
        if payload is None:
            return False

        if callable(is_latest_request):
            if not bool(is_latest_request(request_id)):
                return False

        if isinstance(payload, dict):
            payload["reason_code"] = str(reason_code or "QUEUE_FULL_STALE")
            payload.setdefault("reason", "queue_full_stale")
        if callable(mutate_payload):
            try:
                payload = mutate_payload(payload)
            except (RuntimeError, TypeError, ValueError, AttributeError) as exc:
                self._log_warning(f"[{reason_code}] mutate stale payload gagal: {exc}")
                return False

        try:
            apply_callback(payload)
            self._log_info(f"[{reason_code}] memakai stale payload cache_key={cache_key}")
            return True
        except (RuntimeError, TypeError, ValueError, AttributeError) as exc:
            self._log_warning(f"[{reason_code}] apply stale payload gagal: {exc}")
            return False

    def mark_retry_scheduled(self, schedule_key):
        key = self._normalize_key(schedule_key)
        if not key:
            return False
        with self._lock:
            if key in self._scheduled_retry_keys:
                return False
            self._scheduled_retry_keys.add(key)
        return True

    def clear_retry_scheduled(self, schedule_key):
        key = self._normalize_key(schedule_key)
        if not key:
            return False
        with self._lock:
            self._scheduled_retry_keys.discard(key)
        return True
