"""Tests for volatility signature plot module."""

import math
import pytest
from horizon._horizon import (
    SignaturePlot,
    volatility_signature,
    two_scale_realized_vol,
    noise_variance_estimate,
    optimal_sampling_frequency,
)


def _make_prices(n=500, seed=42):
    """Generate a random walk price series."""
    prices = [100.0]
    state = seed
    for _ in range(n - 1):
        state = (state * 1103515245 + 12345) & 0x7FFFFFFF
        r = ((state / 0x7FFFFFFF) * 2.0 - 1.0) * 0.01
        prices.append(prices[-1] * (1.0 + r))
    return prices


def _make_noisy_prices(n=500, seed=42, noise=0.001):
    """Generate prices with microstructure noise."""
    prices = _make_prices(n, seed)
    state = seed + 1
    noisy = []
    for p in prices:
        state = (state * 1103515245 + 12345) & 0x7FFFFFFF
        eps = ((state / 0x7FFFFFFF) * 2.0 - 1.0) * noise * p
        noisy.append(p + eps)
    return noisy


class TestVolatilitySignature:
    def test_basic(self):
        prices = _make_prices()
        sig = volatility_signature(prices)
        assert isinstance(sig, SignaturePlot)
        assert len(sig.frequencies) > 0
        assert len(sig.realized_vols) == len(sig.frequencies)
        assert sig.optimal_frequency >= 1
        assert sig.two_scale_rv >= 0.0
        assert sig.noise_variance >= 0.0

    def test_repr(self):
        prices = _make_prices()
        sig = volatility_signature(prices)
        assert "SignaturePlot" in repr(sig)

    def test_custom_params(self):
        prices = _make_prices(200)
        sig = volatility_signature(prices, max_freq=50, n_points=10)
        assert len(sig.frequencies) <= 10
        assert all(f <= 50 for f in sig.frequencies)

    def test_frequencies_increasing(self):
        prices = _make_prices()
        sig = volatility_signature(prices)
        for i in range(1, len(sig.frequencies)):
            assert sig.frequencies[i] >= sig.frequencies[i - 1]

    def test_too_few_prices(self):
        with pytest.raises(ValueError):
            volatility_signature([100.0, 101.0, 102.0])

    def test_noisy_vs_clean(self):
        clean = _make_prices(500, seed=123)
        noisy = _make_noisy_prices(500, seed=123, noise=0.005)
        sig_clean = volatility_signature(clean)
        sig_noisy = volatility_signature(noisy)
        # Noisy prices should have higher noise variance estimate
        assert sig_noisy.noise_variance >= 0.0


class TestTwoScaleRealizedVol:
    def test_basic(self):
        prices = _make_prices()
        tsrv = two_scale_realized_vol(prices, slow_freq=5)
        assert tsrv >= 0.0

    def test_different_frequencies(self):
        prices = _make_prices()
        tsrv2 = two_scale_realized_vol(prices, slow_freq=2)
        tsrv10 = two_scale_realized_vol(prices, slow_freq=10)
        # Both should be non-negative
        assert tsrv2 >= 0.0
        assert tsrv10 >= 0.0

    def test_too_few_prices(self):
        with pytest.raises(ValueError):
            two_scale_realized_vol([100.0, 101.0], slow_freq=5)


class TestNoiseVarianceEstimate:
    def test_clean_prices(self):
        # Random walk without noise
        prices = _make_prices(100)
        nv = noise_variance_estimate(prices)
        assert nv >= 0.0

    def test_noisy_prices(self):
        noisy = _make_noisy_prices(500, noise=0.005)
        nv = noise_variance_estimate(noisy)
        assert nv >= 0.0

    def test_too_few(self):
        nv = noise_variance_estimate([100.0])
        assert nv == 0.0

    def test_constant_prices(self):
        nv = noise_variance_estimate([100.0] * 50)
        assert nv == 0.0


class TestOptimalSamplingFrequency:
    def test_basic(self):
        prices = _make_prices()
        freq = optimal_sampling_frequency(prices)
        assert freq >= 1

    def test_short_series(self):
        freq = optimal_sampling_frequency([100.0, 101.0, 102.0])
        assert freq >= 1
