import datetime
import logging
import threading
import time

logger = logging.getLogger(__name__)


class ProgressLogger:
    def __init__(self, interval: float = 5.0):
        """
        :param interval: seconds between logging updates
        """
        self._interval = interval
        self._seconds_read = 0.0
        self._seconds_process = 0.0
        self._seconds_write = 0.0
        self._processed_num_spectra = 0
        self._expected_num_spectra = 0

        self._lock = threading.Lock()
        self._stop_event = threading.Event()
        self._thread = None
        self._t0 = None

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.stop()

    def start(self):
        """Start background logging."""
        self._stop_event.clear()
        if self._thread is None or not self._thread.is_alive():
            self._thread = threading.Thread(target=self._run, daemon=True)
            self._thread.start()

    def stop(self):
        """Stop background logging."""
        self._stop_event.set()
        if self._thread is not None:
            self._thread.join()

    def _run(self):
        """Background thread method."""
        self._t0 = time.perf_counter()
        while not self._stop_event.is_set():
            time.sleep(self._interval)
            self.log_progress()

    def log_progress(self):
        """Log current progress."""
        with self._lock:
            self._log_progress()

    def _log_progress(self):
        total_time = self._seconds_read + self._seconds_process + self._seconds_write
        pct_read = (self._seconds_read / total_time * 100) if total_time else 0
        pct_process = (self._seconds_process / total_time * 100) if total_time else 0
        pct_write = (self._seconds_write / total_time * 100) if total_time else 0

        # Compute milliseconds per spectrum
        if self._processed_num_spectra > 0:
            rate_total = (total_time / self._processed_num_spectra) * 1000
            rate_read = (self._seconds_read / self._processed_num_spectra) * 1000
            rate_process = (self._seconds_process / self._processed_num_spectra) * 1000
            rate_write = (self._seconds_write / self._processed_num_spectra) * 1000
        else:
            rate_total = rate_read = rate_process = rate_write = 0.0

        # Elapsed time
        elapsed_time = time.perf_counter() - self._t0
        elapsed_td = datetime.timedelta(seconds=int(elapsed_time))
        elapsed_str = str(elapsed_td)

        # ETA string
        eta_str = ""
        progress = 0.0
        if self._expected_num_spectra and self._processed_num_spectra:
            progress = self._processed_num_spectra / self._expected_num_spectra * 100
            remaining = max(self._expected_num_spectra - self._processed_num_spectra, 0)
            eta_seconds = (elapsed_time / self._processed_num_spectra) * remaining
            eta_td = datetime.timedelta(seconds=int(eta_seconds))
            eta_str = f" | ETA: {str(eta_td)}"

        # Dynamically derive field width from expected_num_spectra
        width = (
            len(str(self._expected_num_spectra)) if self._expected_num_spectra else 1
        )

        logger.info(
            f"%{width}s/%{width}s %4.1f %% | %.1f ms/sp | r/p/w %.1f/%.1f/%.1f ms/sp %4.1f/%4.1f/%4.1f %% | Elapsed: %s%s",
            int(self._processed_num_spectra),
            int(self._expected_num_spectra),
            progress,
            rate_total,
            rate_read,
            rate_process,
            rate_write,
            pct_read,
            pct_process,
            pct_write,
            elapsed_str,
            eta_str,
        )

    def accumulate(
        self,
        read=0.0,
        process=0.0,
        write=0.0,
        processed_num_spectra=0,
        expected_num_spectra=0,
    ):
        """Accumulate statistics."""
        with self._lock:
            self._seconds_read += read
            self._seconds_process += process
            self._seconds_write += write
            self._processed_num_spectra += processed_num_spectra
            self._expected_num_spectra += expected_num_spectra
