"""Tests for PerilVineDiagnostics computation."""

from __future__ import annotations

import numpy as np
import pandas as pd
import pytest

from insurance_copula import PerilVine, PerilVineDiagnostics, HAS_PYVINECOPULIB
from insurance_copula._diagnostics import make_empty_tau_matrix

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


class TestMakeEmptyTauMatrix:
    def test_identity_matrix(self):
        tau = make_empty_tau_matrix(PERILS)
        np.testing.assert_allclose(np.diag(tau.values), 1.0)

    def test_off_diagonal_zero(self):
        tau = make_empty_tau_matrix(PERILS)
        d = len(PERILS)
        off_diag = tau.values[~np.eye(d, dtype=bool)]
        np.testing.assert_allclose(off_diag, 0.0)

    def test_labels(self):
        tau = make_empty_tau_matrix(PERILS)
        assert list(tau.index) == PERILS
        assert list(tau.columns) == PERILS

    def test_two_perils(self):
        tau = make_empty_tau_matrix(["a", "b"])
        assert tau.shape == (2, 2)
        np.testing.assert_allclose(tau.loc["a", "a"], 1.0)
        np.testing.assert_allclose(tau.loc["a", "b"], 0.0)


class TestDiagnosticsFromFittedVine:
    @REQUIRES_PV
    def test_returns_diagnostics_object(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert isinstance(diag, PerilVineDiagnostics)

    @REQUIRES_PV
    def test_tau_matrix_shape(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert diag.kendall_tau_matrix.shape == (4, 4)

    @REQUIRES_PV
    def test_tau_matrix_labels(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert list(diag.kendall_tau_matrix.index) == PERILS
        assert list(diag.kendall_tau_matrix.columns) == PERILS

    @REQUIRES_PV
    def test_upper_td_shape(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert diag.tail_dependence_upper.shape == (4, 4)

    @REQUIRES_PV
    def test_lower_td_shape(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert diag.tail_dependence_lower.shape == (4, 4)

    @REQUIRES_PV
    def test_aic_bic_are_finite(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert np.isfinite(diag.aic)
        assert np.isfinite(diag.bic)

    @REQUIRES_PV
    def test_bic_gte_aic_or_close(self, fitted_vine):
        """BIC penalises complexity more — usually >= AIC for same model."""
        diag = fitted_vine.diagnostics()
        # Both should be finite and reasonably sized
        assert abs(diag.bic) < 1e8
        assert abs(diag.aic) < 1e8

    @REQUIRES_PV
    def test_pair_copulas_nonempty(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert len(diag.pair_copulas) > 0

    @REQUIRES_PV
    def test_pair_copulas_have_required_keys(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        for pc in diag.pair_copulas:
            assert "tree" in pc
            assert "edge" in pc
            assert "family" in pc
            assert "params" in pc
            assert "tau" in pc

    @REQUIRES_PV
    def test_n_parameters_non_negative(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        assert diag.n_parameters >= 0

    @REQUIRES_PV
    def test_repr_contains_aic_bic(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        r = repr(diag)
        assert "AIC" in r
        assert "BIC" in r

    @REQUIRES_PV
    def test_summary_table_has_correct_rows(self, fitted_vine):
        diag = fitted_vine.diagnostics()
        table = diag.summary_table()
        assert len(table) == len(diag.pair_copulas)

    @REQUIRES_PV
    def test_2peril_vine_diagnostics(self, fitted_vine_2perils):
        diag = fitted_vine_2perils.diagnostics()
        assert diag.kendall_tau_matrix.shape == (2, 2)
        # 2-peril vine has exactly 1 pair copula
        assert len(diag.pair_copulas) == 1

    def test_unfitted_raises(self):
        vine = PerilVine(perils=PERILS)
        with pytest.raises(RuntimeError, match="not been fitted"):
            vine.diagnostics()
