"""Tests for utility functions: filtering, rate interpolation, parallel/serial."""
import pytest
import pandas as pd
import numpy as np

from qmoms import default_params, default_surf_dtype_mapping, filter_options, get_rate_for_maturity


class TestFilterOptions:

    def test_output_shape(self, raw_data):
        df_surf, _, _ = raw_data
        df = df_surf[default_surf_dtype_mapping.keys()].copy()
        result = filter_options(df, default_params['filter'])
        assert result.shape == (63750, 6)

    def test_moneyness_within_limits(self, raw_data):
        df_surf, _, _ = raw_data
        df = df_surf[default_surf_dtype_mapping.keys()].copy()
        result = filter_options(df, default_params['filter'])
        lim = default_params['filter']['mnes_lim']
        assert result['mnes'].min() >= lim[0]
        assert result['mnes'].max() <= lim[1]

    def test_delta_within_limits(self, raw_data):
        df_surf, _, _ = raw_data
        df = df_surf[default_surf_dtype_mapping.keys()].copy()
        result = filter_options(df, default_params['filter'])
        d_put = default_params['filter']['delta_put_lim']
        d_call = default_params['filter']['delta_call_lim']
        puts = result[result['delta'] < 0]
        calls = result[result['delta'] >= 0]
        assert puts['delta'].min() >= d_put[0]
        assert calls['delta'].max() <= d_call[1]

    def test_sorted_by_id_date_mnes(self, raw_data):
        df_surf, _, _ = raw_data
        df = df_surf[default_surf_dtype_mapping.keys()].copy()
        result = filter_options(df, default_params['filter'])
        for _, grp in result.groupby(['id', 'date']):
            assert grp['mnes'].is_monotonic_increasing

    def test_custom_strict_filter(self, raw_data):
        df_surf, _, _ = raw_data
        df = df_surf[default_surf_dtype_mapping.keys()].copy()
        strict_filter = default_params['filter'].copy()
        strict_filter['mnes_lim'] = [0.9, 1.1]
        result = filter_options(df, strict_filter)
        assert result.shape[0] < 63750
        assert result['mnes'].min() >= 0.9
        assert result['mnes'].max() <= 1.1


class TestGetRateForMaturity:

    def test_single_rate(self, raw_data):
        _, df_rate, _ = raw_data
        rate = get_rate_for_maturity(df_rate, date=pd.Timestamp('2023-01-03'), days=30)
        assert isinstance(rate, (float, np.floating))
        assert 0 < rate < 0.1  # reasonable range in decimals

    def test_known_rate_value(self, raw_data, first_group, first_group_rate):
        rate = first_group_rate
        assert abs(rate - 0.0411173900) < 1e-6

    def test_rate_interpolation(self, raw_data):
        """Rate for an intermediate maturity should be between neighboring values."""
        _, df_rate, _ = raw_data
        date = df_rate['date'].iloc[0]
        rate_30 = get_rate_for_maturity(df_rate, date=date, days=30)
        rate_60 = get_rate_for_maturity(df_rate, date=date, days=60)
        rate_45 = get_rate_for_maturity(df_rate, date=date, days=45)
        assert min(rate_30, rate_60) <= rate_45 <= max(rate_30, rate_60)

    def test_surface_rate_merge(self, raw_data, filtered_surf):
        _, df_rate, _ = raw_data
        result = get_rate_for_maturity(df_rate, df_surf=filtered_surf.copy())
        assert 'rate' in result.columns
        assert result['rate'].notna().all()
        assert result.shape[0] >= filtered_surf.shape[0]

    def test_missing_inputs_returns_nan(self, raw_data):
        _, df_rate, _ = raw_data
        result = get_rate_for_maturity(df_rate, date=None, days=None)
        assert np.isnan(result)

    def test_missing_date_uses_last_available(self, raw_data):
        """If exact date not found, should fall back to last available."""
        _, df_rate, _ = raw_data
        rate = get_rate_for_maturity(df_rate, date=pd.Timestamp('2023-01-01'), days=30)
        assert isinstance(rate, (float, np.floating))
        assert not np.isnan(rate)
