import threading
import time

from pypos.core.base_service import BaseService
from pypos.modules.sinkronisasi.services.export_upload_service import ExportUploadService
from pypos.modules.sinkronisasi.services.transaction_export_service import TransactionExportService


class ExportCycleService(BaseService):
    _guard_lock = threading.Lock()
    _guard_running = False
    _guard_source = ""
    _guard_started_at = 0.0

    def __init__(self, export_service=None, upload_service=None):
        super().__init__()
        self.export_service = export_service or TransactionExportService()
        self.upload_service = upload_service or ExportUploadService()

    # edited by glg
    def _emit_progress(self, callback, percent):
        if not callable(callback):
            return
        try:
            value = int(percent)
        except (TypeError, ValueError, OverflowError):
            value = 0
        value = max(0, min(100, value))
        try:
            callback(value)
        except (RuntimeError, TypeError, ValueError, AttributeError):
            pass

    @classmethod
    def is_running(cls):
        with cls._guard_lock:
            return bool(cls._guard_running)

    @classmethod
    def guard_state(cls):
        with cls._guard_lock:
            return {
                "running": bool(cls._guard_running),
                "source": str(cls._guard_source or ""),
                "started_at": float(cls._guard_started_at or 0.0),
            }

    @classmethod
    def _try_enter_guard(cls, source):
        with cls._guard_lock:
            if cls._guard_running:
                return False
            cls._guard_running = True
            cls._guard_source = str(source or "")
            cls._guard_started_at = time.time()
            return True

    @classmethod
    def _leave_guard(cls):
        with cls._guard_lock:
            cls._guard_running = False
            cls._guard_source = ""
            cls._guard_started_at = 0.0

    # edited by glg
    def run_cycle(
        self,
        source="timer",
        progress_callback=None,
        skip_upload=False,
        upload_limit=None,
        requeue_failed_transient=False,
        upload_timeout_override=None,
    ):
        if not self._try_enter_guard(source):
            state = self.guard_state()
            return {
                "ran": False,
                "skipped": "already_running",
                "guard_source": state.get("source", ""),
                "guard_started_at": state.get("started_at", 0.0),
                "exported_rows": 0,
                "uploaded": 0,
                "failed": 0,
                "retried": 0,
                "cleaned_files": 0,
                "cleaned_empty_flux": 0,
                "retry_items": [],
                "failed_items": [],
                "requeued_failed_transient": 0,
                "skipped_upload": "",
            }

        try:
            self._emit_progress(progress_callback, 5)

            def _map_export_progress(stage_percent):
                try:
                    stage = int(stage_percent)
                except (TypeError, ValueError, OverflowError):
                    stage = 0
                stage = max(0, min(100, stage))
                # Bobot export: 5..60
                self._emit_progress(progress_callback, 5 + int(stage * 55 / 100))

            try:
                exported_rows = int(
                    self.export_service.export_batch(progress_callback=_map_export_progress) or 0
                )
            except TypeError:
                exported_rows = int(self.export_service.export_batch() or 0)

            self._emit_progress(progress_callback, 60)

            if bool(skip_upload):
                self._emit_progress(progress_callback, 100)
                return {
                    "ran": True,
                    "skipped": "",
                    "guard_source": str(source or ""),
                    "guard_started_at": 0.0,
                    "exported_rows": exported_rows,
                    "uploaded": 0,
                    "failed": 0,
                    "retried": 0,
                    "cleaned_files": 0,
                    "cleaned_empty_flux": 0,
                    "retry_items": [],
                    "failed_items": [],
                    "requeued_failed_transient": 0,
                    "skipped_upload": "skip_upload_requested",
                }

            def _map_upload_progress(stage_percent):
                try:
                    stage = int(stage_percent)
                except (TypeError, ValueError, OverflowError):
                    stage = 0
                stage = max(0, min(100, stage))
                # Bobot upload: 60..95
                self._emit_progress(progress_callback, 60 + int(stage * 35 / 100))

            try:
                upload_result = self.upload_service.process_pending_uploads(
                    progress_callback=_map_upload_progress,
                    limit_override=upload_limit,
                    requeue_failed_transient=requeue_failed_transient,
                    timeout_override=upload_timeout_override,
                ) or {}
            except TypeError:
                upload_result = self.upload_service.process_pending_uploads() or {}

            self._emit_progress(progress_callback, 100)
            return {
                "ran": True,
                "skipped": "",
                "guard_source": str(source or ""),
                "guard_started_at": 0.0,
                "exported_rows": exported_rows,
                "uploaded": int(upload_result.get("uploaded") or 0),
                "failed": int(upload_result.get("failed") or 0),
                "retried": int(upload_result.get("retried") or 0),
                "cleaned_files": int(upload_result.get("cleaned_files") or 0),
                "cleaned_empty_flux": int(upload_result.get("cleaned_empty_flux") or 0),
                "retry_items": upload_result.get("retry_items") if isinstance(upload_result.get("retry_items"), list) else [],
                "failed_items": upload_result.get("failed_items") if isinstance(upload_result.get("failed_items"), list) else [],
                "requeued_failed_transient": int(upload_result.get("requeued_failed_transient") or 0),
                "skipped_upload": "",
            }
        finally:
            self._leave_guard()
