"""Tests for conditional expectation estimation."""

from __future__ import annotations

import warnings

import numpy as np
import pandas as pd
import pytest
from scipy.stats import gamma

from insurance_copula import PerilVine, HAS_PYVINECOPULIB

PERILS = ["flood", "subsidence", "storm", "eow"]
REQUIRES_PV = pytest.mark.skipif(
    not HAS_PYVINECOPULIB,
    reason="pyvinecopulib not available on this platform",
)


class TestConditionalExpectation:
    @REQUIRES_PV
    def test_basic_returns_float(self, fitted_vine, marginals_4perils):
        """E[flood | storm > median] should return a float."""
        threshold = gamma(a=1.2, scale=800).ppf(0.5)
        result = fitted_vine.conditional_expectation(
            target_peril="flood",
            condition_peril="storm",
            threshold=threshold,
            n_samples=10_000,
            marginals=marginals_4perils,
        )
        assert isinstance(result, float)
        assert result > 0

    @REQUIRES_PV
    def test_conditional_gt_unconditional_for_correlated(
        self, fitted_vine, marginals_4perils
    ):
        """Conditioning on above-median storm loss should raise E[eow] (correlated)."""
        storm_median = gamma(a=1.2, scale=800).ppf(0.5)
        conditional_eow = fitted_vine.conditional_expectation(
            target_peril="eow",
            condition_peril="storm",
            threshold=storm_median,
            n_samples=20_000,
            marginals=marginals_4perils,
        )
        unconditional_eow = gamma(a=1.5, scale=600).mean()
        # Should be higher than unconditional mean due to positive dependence
        assert conditional_eow > unconditional_eow * 0.8  # generous bound

    @REQUIRES_PV
    def test_copula_space_no_marginals(self, fitted_vine):
        """Without marginals, threshold is on [0,1]."""
        result = fitted_vine.conditional_expectation(
            target_peril="flood",
            condition_peril="storm",
            threshold=0.5,
            n_samples=5_000,
        )
        assert isinstance(result, float)
        # Result should be in [0,1] for copula space
        assert 0 <= result <= 1

    @REQUIRES_PV
    def test_very_high_threshold_warns(self, fitted_vine):
        """Very high threshold produces few passing samples — should warn."""
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            result = fitted_vine.conditional_expectation(
                target_peril="flood",
                condition_peril="storm",
                threshold=0.9999,
                n_samples=2_000,
            )
            # May or may not warn depending on exact samples; just check it runs
        assert isinstance(result, float)

    @REQUIRES_PV
    def test_invalid_target_peril_raises(self, fitted_vine):
        with pytest.raises(ValueError, match="target_peril"):
            fitted_vine.conditional_expectation(
                target_peril="fire",
                condition_peril="storm",
                threshold=0.5,
                n_samples=1_000,
            )

    @REQUIRES_PV
    def test_invalid_condition_peril_raises(self, fitted_vine):
        with pytest.raises(ValueError, match="condition_peril"):
            fitted_vine.conditional_expectation(
                target_peril="flood",
                condition_peril="fire",
                threshold=0.5,
                n_samples=1_000,
            )

    def test_unfitted_raises(self):
        vine = PerilVine(perils=PERILS)
        with pytest.raises(RuntimeError, match="not been fitted"):
            vine.conditional_expectation(
                target_peril="flood",
                condition_peril="storm",
                threshold=0.5,
            )

    @REQUIRES_PV
    def test_zero_samples_raises(self, fitted_vine):
        with pytest.raises(ValueError, match="n_samples must be positive"):
            fitted_vine.conditional_expectation(
                target_peril="flood",
                condition_peril="storm",
                threshold=0.5,
                n_samples=0,
            )

    @REQUIRES_PV
    def test_impossible_threshold_raises(self, fitted_vine):
        """Threshold so extreme no samples pass should raise RuntimeError."""
        with pytest.raises(RuntimeError, match="No samples passed"):
            fitted_vine.conditional_expectation(
                target_peril="flood",
                condition_peril="storm",
                threshold=1.1,  # Above 1 — impossible in copula space
                n_samples=1_000,
            )

    @REQUIRES_PV
    def test_threshold_at_zero(self, fitted_vine):
        """Threshold at 0 means all samples pass — should equal unconditional mean."""
        result = fitted_vine.conditional_expectation(
            target_peril="flood",
            condition_peril="storm",
            threshold=0.0,
            n_samples=5_000,
        )
        # All samples pass; result should be close to E[flood] in [0,1]
        assert 0 < result < 1

    @REQUIRES_PV
    def test_same_peril_target_and_condition(self, fitted_vine):
        """E[X | X > t] should be > t for t in (0,1)."""
        result = fitted_vine.conditional_expectation(
            target_peril="flood",
            condition_peril="flood",
            threshold=0.5,
            n_samples=5_000,
        )
        assert result > 0.5

    @REQUIRES_PV
    def test_2peril_vine(self, fitted_vine_2perils):
        result = fitted_vine_2perils.conditional_expectation(
            target_peril="flood",
            condition_peril="storm",
            threshold=0.5,
            n_samples=5_000,
        )
        assert isinstance(result, float)
        assert 0 <= result <= 1
