"""Gaussian mechanism for (ε, δ)-differential-privacy-based unlearning.

Moved from sage.libs.privacy.unlearning.algorithms — concrete implementation
belongs in sage-privacy (L3 research).
"""

from __future__ import annotations

import math

import numpy as np

from sage_privacy._base import BasePrivacyMechanism


class GaussianMechanism(BasePrivacyMechanism):
    """Gaussian mechanism for (ε, δ)-differential privacy.

    The mechanism adds noise sampled from N(0, σ²) where σ is calibrated as::

        σ = Δf · √(2 · ln(1.25 / δ)) / ε

    This achieves (ε, δ)-DP for the given sensitivity and privacy parameters.

    Args:
        epsilon: Privacy parameter ε > 0.
        delta: Failure probability δ ∈ (0, 1).
        sensitivity: L2 sensitivity of the query (default: 1.0).

    Example:
        >>> mech = GaussianMechanism(epsilon=1.0, delta=1e-5)
        >>> noisy_value = 42.0 + mech.compute_noise()
    """

    def __init__(
        self,
        epsilon: float,
        delta: float,
        sensitivity: float = 1.0,
    ) -> None:
        super().__init__(
            epsilon=epsilon,
            delta=delta,
            sensitivity=sensitivity,
            name="Gaussian",
        )
        self.sigma = self._compute_sigma(sensitivity, epsilon, delta)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _compute_sigma(sensitivity: float, epsilon: float, delta: float) -> float:
        """Compute required σ for (ε, δ)-DP using the standard calibration formula."""
        if delta <= 0 or delta >= 1:
            raise ValueError(f"delta must be in (0, 1), got {delta}")
        return sensitivity * math.sqrt(2 * math.log(1.25 / delta)) / epsilon

    # ------------------------------------------------------------------
    # Protocol methods
    # ------------------------------------------------------------------

    def compute_noise(
        self,
        sensitivity: float | None = None,
        epsilon: float | None = None,
        delta: float | None = None,
    ) -> float:
        """Generate Gaussian noise N(0, σ²).

        If parameter overrides are provided, σ is recomputed on-the-fly.

        Args:
            sensitivity: Override instance sensitivity.
            epsilon: Override instance epsilon.
            delta: Override instance delta.

        Returns:
            A single noise value drawn from N(0, σ²).
        """
        if sensitivity is not None or epsilon is not None or delta is not None:
            sigma = self._compute_sigma(
                sensitivity if sensitivity is not None else self.sensitivity,
                epsilon if epsilon is not None else self.epsilon,
                delta if delta is not None else (self.delta or 1e-5),
            )
        else:
            sigma = self.sigma

        return float(np.random.normal(0, sigma))

    def privacy_cost(self) -> tuple[float, float]:
        """Return the privacy cost (ε, δ) — Gaussian satisfies (ε, δ)-DP."""
        if self.delta is None:
            raise ValueError("Gaussian mechanism requires delta to be set")
        return (self.epsilon, self.delta)
