"""Tests for OptionE convergence detection."""

import numpy as np
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from convergio import detect, watch, rolling_variance, count_modes


def test_converging_signal():
    signal = np.exp(-np.linspace(0, 10, 1000))
    result = detect(signal, var_threshold=1e-6)
    assert result.state == "converged", f"Expected converged, got {result.state}"
    assert result.converged_at is not None
    assert result.quality > 0.5
    assert result.recommendation == "stop"
    print("PASS: converging signal")


def test_oscillating_signal():
    signal = np.sin(np.linspace(0, 100, 2000))
    result = detect(signal, var_threshold=1e-6)
    assert result.state == "oscillating", f"Expected oscillating, got {result.state}"
    assert result.oscillation_freq > 0.001, f"Expected osc_freq > 0.001, got {result.oscillation_freq}"
    assert result.recommendation == "continue"
    print("PASS: oscillating signal")


def test_diverging_signal():
    np.random.seed(42)
    signal = np.cumsum(np.random.randn(1000) * 0.5)
    result = detect(signal, var_threshold=1e-4)
    assert result.state == "diverging", f"Expected diverging, got {result.state}"
    assert result.recommendation == "restart"
    print("PASS: diverging signal")


def test_constant_signal():
    signal = np.ones(500) * 3.14 + np.random.randn(500) * 1e-8
    result = detect(signal, var_threshold=1e-6)
    assert result.state == "converged", f"Expected converged, got {result.state}"
    assert result.quality > 0.9
    print("PASS: constant signal")


def test_bistable_signal():
    np.random.seed(42)
    signal = np.zeros(2000)
    state = 1.0
    for i in range(2000):
        if np.random.random() < 0.02:
            state = -state
        signal[i] = state + np.random.randn() * 0.05
    result = detect(signal, var_threshold=0.001)
    assert result.n_modes >= 2, f"Expected >=2 modes, got {result.n_modes}"
    print("PASS: bistable signal")


def test_short_signal():
    signal = [1.0, 2.0, 3.0]
    result = detect(signal)
    assert result.state == "warming_up"
    assert result.recommendation == "continue"
    print("PASS: short signal")


def test_rolling_variance():
    signal = np.ones(100)
    rv = rolling_variance(signal, window=10)
    assert len(rv) == 91
    assert np.all(rv < 1e-10)
    print("PASS: rolling variance")


def test_count_modes_unimodal():
    signal = np.random.randn(1000) + 5.0
    n = count_modes(signal)
    assert n == 1, f"Expected 1 mode, got {n}"
    print("PASS: count modes unimodal")


def test_count_modes_bimodal():
    signal = np.concatenate([
        np.random.randn(500) - 3.0,
        np.random.randn(500) + 3.0
    ])
    n = count_modes(signal)
    assert n >= 2, f"Expected >=2 modes, got {n}"
    print("PASS: count modes bimodal")


def test_watch_early_stopping():
    mon = watch(var_threshold=1e-6, check_every=50, min_steps=100)
    stopped = False
    for i in range(5000):
        value = 10.0 * np.exp(-i / 100.0) + np.random.randn() * 1e-8
        stop, result = mon.step(value)
        if stop:
            stopped = True
            assert i < 2000, f"Should have stopped much earlier, stopped at {i}"
            break
    assert stopped, "Watch should have triggered early stopping"
    print(f"PASS: watch early stopping (stopped at step {i})")


def test_watch_reset():
    mon = watch(var_threshold=1e-4)
    for i in range(200):
        mon.step(float(i))
    assert mon.n_steps == 200
    mon.reset()
    assert mon.n_steps == 0
    print("PASS: watch reset")


if __name__ == "__main__":
    tests = [
        test_converging_signal,
        test_oscillating_signal,
        test_diverging_signal,
        test_constant_signal,
        test_bistable_signal,
        test_short_signal,
        test_rolling_variance,
        test_count_modes_unimodal,
        test_count_modes_bimodal,
        test_watch_early_stopping,
        test_watch_reset,
    ]

    passed = 0
    failed = 0

    for t in tests:
        try:
            t()
            passed += 1
        except Exception as e:
            print(f"FAIL: {t.__name__}: {e}")
            failed += 1

    print(f"\n{'='*40}")
    print(f"Results: {passed} passed, {failed} failed")
    print(f"{'='*40}")
