"""Aggregate portfolio loss simulation.

The aggregate loss for a portfolio of n_policies is the sum of all
individual policy losses across all perils. We simulate it by:

1. Drawing n_policies independent samples from the vine copula.
2. Back-transforming to loss space using marginal PPFs.
3. Summing all losses (all perils, all policies) to get one scenario.
4. Repeating n_sim times to build an empirical distribution.

This is equivalent to assuming policies are independent of each other
(which is a reasonable simplification for most portfolio calculations —
the correlation structure between perils is what the copula captures).
If you want between-policy correlation (e.g. flood affecting a whole
postcode), you'd need a hierarchical model — outside scope here.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
    from scipy.stats import rv_continuous
    from ._results import AggregateResult


def simulate_aggregate_loss(
    vine: object,
    n_sim: int,
    marginals: dict[str, "rv_continuous"],
    perils: list[str],
    n_policies: int,
    seed: int | None = None,
) -> "AggregateResult":
    """Simulate aggregate portfolio loss distribution.

    Parameters
    ----------
    vine:
        Fitted ``pyvinecopulib.Vinecop`` instance.
    n_sim:
        Number of scenario simulations.
    marginals:
        Fitted scipy distributions for each peril.
    perils:
        Ordered peril names matching vine dimensions.
    n_policies:
        Number of policies in the portfolio.
    seed:
        Base random seed. Each simulation uses ``seed + i`` if provided.

    Returns
    -------
    AggregateResult
        Empirical aggregate loss distribution with PML quantiles.

    Raises
    ------
    ValueError
        If n_sim or n_policies is not positive.
    """
    from ._marginals import back_transform, validate_marginals
    from ._results import AggregateResult

    if n_sim < 1:
        raise ValueError(f"n_sim must be positive, got {n_sim}")
    if n_policies < 1:
        raise ValueError(f"n_policies must be positive, got {n_policies}")

    validate_marginals(perils, marginals)

    agg_losses = np.empty(n_sim, dtype=float)

    for i in range(n_sim):
        sim_seed: list[int] = [seed + i] if seed is not None else []
        u = vine.simulate(n_policies, seeds=sim_seed)  # (n_policies, d)
        losses_df = back_transform(u, perils, marginals)
        agg_losses[i] = float(losses_df.values.sum())

    expected_loss = float(agg_losses.mean())
    std_loss = float(agg_losses.std(ddof=1)) if n_sim > 1 else 0.0
    cv = std_loss / expected_loss if expected_loss > 0 else 0.0

    return AggregateResult(
        simulations=agg_losses,
        pml_1_in_100=float(np.percentile(agg_losses, 99)),
        pml_1_in_200=float(np.percentile(agg_losses, 99.5)),
        expected_loss=expected_loss,
        coefficient_of_variation=cv,
    )
