"""Conditional expectation via accept-reject Monte Carlo.

The standard approach for E[Y | X > t] when we have a vine copula:

1. Simulate unconditionally from the fitted vine (n_samples draws).
2. Back-transform from uniform to loss space using marginal PPFs.
3. Filter rows where the conditioning variable exceeds the threshold.
4. Return the mean of the target variable in the filtered set.

This is exact in expectation and converges at n^{-1/2}. For rare
thresholds (above the 99th percentile), 50,000 samples typically gives
at least 500 passing — enough for a stable estimate. Below that,
increase n_samples.
"""

from __future__ import annotations

import warnings
from typing import TYPE_CHECKING

import numpy as np
import pandas as pd

if TYPE_CHECKING:
    from scipy.stats import rv_continuous

_MIN_PASSING_SAMPLES = 500


def conditional_expectation(
    vine: object,
    target_peril: str,
    condition_peril: str,
    threshold: float,
    perils: list[str],
    n_samples: int = 50_000,
    marginals: dict[str, "rv_continuous"] | None = None,
    seed: int | None = None,
) -> float:
    """Compute E[target_peril | condition_peril > threshold].

    Uses accept-reject Monte Carlo: simulate unconditionally then filter.
    This is unbiased and consistent but variance grows as the threshold
    moves into the tail.

    Parameters
    ----------
    vine:
        Fitted ``pyvinecopulib.Vinecop`` instance.
    target_peril:
        The peril whose expected loss we want.
    condition_peril:
        The conditioning peril.
    threshold:
        Conditioning threshold in loss space (if marginals provided) or
        on [0,1] (if marginals is None).
    perils:
        Ordered list of peril names matching vine dimensions.
    n_samples:
        Number of unconditional draws. Increase for rare thresholds.
    marginals:
        Optional dict of fitted scipy distributions for back-transformation.
        If None, the threshold and output are on [0,1].
    seed:
        Random seed for reproducibility.

    Returns
    -------
    float
        Estimated conditional expectation.

    Raises
    ------
    ValueError
        If target_peril or condition_peril not in perils.
    RuntimeError
        If zero samples pass the filter.
    """
    if target_peril not in perils:
        raise ValueError(
            f"target_peril '{target_peril}' not in perils {perils}"
        )
    if condition_peril not in perils:
        raise ValueError(
            f"condition_peril '{condition_peril}' not in perils {perils}"
        )
    if n_samples < 1:
        raise ValueError(f"n_samples must be positive, got {n_samples}")

    seeds: list[int] = [seed] if seed is not None else []
    u = vine.simulate(n_samples, seeds=seeds)  # (n_samples, d)

    if marginals is not None:
        from ._marginals import back_transform
        losses = back_transform(u, perils, marginals)
        condition_values = losses[condition_peril].values
        target_values = losses[target_peril].values
    else:
        cond_idx = perils.index(condition_peril)
        tgt_idx = perils.index(target_peril)
        condition_values = u[:, cond_idx]
        target_values = u[:, tgt_idx]

    mask = condition_values > threshold
    n_passing = int(mask.sum())

    if n_passing == 0:
        raise RuntimeError(
            f"No samples passed the filter condition_peril='{condition_peril}' "
            f"> {threshold}. Increase n_samples or lower the threshold."
        )

    if n_passing < _MIN_PASSING_SAMPLES:
        warnings.warn(
            f"Only {n_passing} samples passed the filter "
            f"(condition_peril='{condition_peril}' > {threshold}). "
            f"The conditional expectation estimate may be unreliable. "
            f"Consider increasing n_samples (current: {n_samples:,}).",
            UserWarning,
            stacklevel=3,
        )

    return float(target_values[mask].mean())
