"""
Strategia backoff esponenziale per riconnessioni IMAP.

Parametri configurabili in WorkerSettings:
  backoff_initial_seconds  = 1.0   (primo wait)
  backoff_multiplier       = 2.0   (moltiplicatore)
  backoff_max_seconds      = 300.0 (tetto massimo: 5 minuti)

Sequenza di attesa: 1s → 2s → 4s → 8s → 16s → 32s → 64s → 128s → 256s → 300s → 300s → ...
"""

import asyncio
import logging
import random

from app.config import get_settings

logger = logging.getLogger(__name__)
settings = get_settings()


class ExponentialBackoff:
    """
    Gestisce il backoff esponenziale con jitter opzionale.

    Uso:
        backoff = ExponentialBackoff(label="casella@pec.it")
        while True:
            try:
                await connect()
                backoff.reset()      # connessione riuscita → resetta backoff
                await run_loop()
            except Exception as e:
                await backoff.wait(e)  # attende prima di ritentare
    """

    def __init__(self, label: str = "", jitter: bool = True) -> None:
        self.label = label
        self.jitter = jitter
        self._attempt = 0
        self._current_wait = settings.backoff_initial_seconds

    def reset(self) -> None:
        """Resetta il backoff dopo una connessione riuscita."""
        if self._attempt > 0:
            logger.info(
                f"[{self.label}] Connessione ristabilita dopo {self._attempt} tentativi"
            )
        self._attempt = 0
        self._current_wait = settings.backoff_initial_seconds

    async def wait(self, error: Exception | None = None) -> None:
        """
        Attende il tempo calcolato dal backoff, poi incrementa per il prossimo ciclo.
        Aggiunge jitter ±10% per evitare thundering herd su N caselle.
        """
        self._attempt += 1
        wait_time = min(self._current_wait, settings.backoff_max_seconds)

        if self.jitter:
            jitter_range = wait_time * 0.1
            wait_time += random.uniform(-jitter_range, jitter_range)
            wait_time = max(0.5, wait_time)

        logger.warning(
            f"[{self.label}] Tentativo {self._attempt} fallito"
            f"{f': {error}' if error else ''}. "
            f"Riconnessione in {wait_time:.1f}s"
        )

        await asyncio.sleep(wait_time)

        # Incrementa per il prossimo tentativo
        self._current_wait = min(
            self._current_wait * settings.backoff_multiplier,
            settings.backoff_max_seconds,
        )

    @property
    def attempt(self) -> int:
        return self._attempt
