"""Result dataclasses for insurance-copula outputs."""

from __future__ import annotations

from dataclasses import dataclass, field

import numpy as np
import pandas as pd


@dataclass
class AggregateResult:
    """Empirical aggregate portfolio loss distribution from vine copula simulation.

    Produced by :meth:`PerilVine.aggregate_loss`. Each element of
    ``simulations`` is the total portfolio loss summed across all policies and
    all perils for one scenario.

    Parameters
    ----------
    simulations:
        Array of length ``n_sim`` aggregate loss values.
    pml_1_in_100:
        Probable maximum loss at the 1-in-100 return period (99th percentile).
    pml_1_in_200:
        Probable maximum loss at the 1-in-200 return period (99.5th percentile,
        the Solvency II SCR level).
    expected_loss:
        Mean aggregate loss across all simulations.
    coefficient_of_variation:
        Standard deviation divided by the mean. Measures relative volatility.
    """

    simulations: np.ndarray
    pml_1_in_100: float
    pml_1_in_200: float
    expected_loss: float
    coefficient_of_variation: float

    def __post_init__(self) -> None:
        self.simulations = np.asarray(self.simulations, dtype=float)
        if self.simulations.ndim != 1:
            raise ValueError("simulations must be a 1-D array")
        if len(self.simulations) == 0:
            raise ValueError("simulations must be non-empty")
        self.pml_1_in_100 = float(self.pml_1_in_100)
        self.pml_1_in_200 = float(self.pml_1_in_200)
        self.expected_loss = float(self.expected_loss)
        self.coefficient_of_variation = float(self.coefficient_of_variation)

    @property
    def n_sim(self) -> int:
        """Number of simulations."""
        return len(self.simulations)

    def percentile(self, p: float) -> float:
        """Return the p-th percentile of the aggregate loss distribution.

        Parameters
        ----------
        p:
            Percentile in [0, 100].
        """
        if not 0.0 <= p <= 100.0:
            raise ValueError(f"p must be in [0, 100], got {p}")
        return float(np.percentile(self.simulations, p))

    def __repr__(self) -> str:
        return (
            f"AggregateResult("
            f"n_sim={self.n_sim:,}, "
            f"E[L]={self.expected_loss:,.0f}, "
            f"PML_1in100={self.pml_1_in_100:,.0f}, "
            f"PML_1in200={self.pml_1_in_200:,.0f}, "
            f"CV={self.coefficient_of_variation:.3f}"
            f")"
        )


@dataclass
class PerilVineDiagnostics:
    """Diagnostics for a fitted :class:`PerilVine` model.

    Produced by :meth:`PerilVine.diagnostics`. Contains the Kendall tau
    matrix, tail dependence coefficients, information criteria, and a
    flat list of pair-copula summaries from every tree.

    Parameters
    ----------
    kendall_tau_matrix:
        ``d x d`` DataFrame of pairwise Kendall's tau values. Diagonal
        is 1.0 by convention.
    tail_dependence_upper:
        ``d x d`` DataFrame of upper tail dependence coefficients.
        Zero for families without upper tail dependence (e.g. Gaussian,
        Clayton).
    tail_dependence_lower:
        ``d x d`` DataFrame of lower tail dependence coefficients.
        Zero for families without lower tail dependence (e.g. Gaussian,
        Gumbel).
    aic:
        Akaike information criterion for the fitted vine.
    bic:
        Bayesian information criterion for the fitted vine.
    n_parameters:
        Total number of free parameters across all pair copulas.
    pair_copulas:
        List of dicts, one per edge, with keys: ``tree``, ``edge``,
        ``family``, ``params``, ``tau``.
    """

    kendall_tau_matrix: pd.DataFrame
    tail_dependence_upper: pd.DataFrame
    tail_dependence_lower: pd.DataFrame
    aic: float
    bic: float
    n_parameters: int
    pair_copulas: list[dict]

    def __post_init__(self) -> None:
        self.aic = float(self.aic)
        self.bic = float(self.bic)
        self.n_parameters = int(self.n_parameters)
        if not isinstance(self.pair_copulas, list):
            raise TypeError("pair_copulas must be a list")

    def summary_table(self) -> pd.DataFrame:
        """Return pair copula summary as a tidy DataFrame.

        Returns
        -------
        pd.DataFrame
            One row per pair copula with columns: tree, edge, family,
            params, tau.
        """
        if not self.pair_copulas:
            return pd.DataFrame(
                columns=["tree", "edge", "family", "params", "tau"]
            )
        return pd.DataFrame(self.pair_copulas)

    def __repr__(self) -> str:
        d = len(self.kendall_tau_matrix)
        return (
            f"PerilVineDiagnostics("
            f"d={d}, "
            f"n_parameters={self.n_parameters}, "
            f"AIC={self.aic:.1f}, "
            f"BIC={self.bic:.1f}, "
            f"n_pair_copulas={len(self.pair_copulas)}"
            f")"
        )
