"""Tests for ACD (Autoregressive Conditional Duration) module."""

import pytest
from horizon._horizon import (
    AcdModel,
    AcdState,
    acd_log_likelihood,
    fit_acd,
)


class TestAcdModel:
    def test_create(self):
        model = AcdModel(omega=0.1, alpha=0.1, beta=0.8)
        assert model is not None
        assert "AcdModel" in repr(model)

    def test_default_params(self):
        model = AcdModel()
        assert model is not None

    def test_invalid_params(self):
        with pytest.raises(ValueError):
            AcdModel(omega=-1.0)
        with pytest.raises(ValueError):
            AcdModel(alpha=0.5, beta=0.6)  # sum >= 1

    def test_first_update(self):
        model = AcdModel()
        state = model.update(1000.0)
        assert isinstance(state, AcdState)
        assert state.actual_duration == 0.0
        assert state.surprise == 1.0

    def test_sequential_updates(self):
        model = AcdModel(omega=0.1, alpha=0.1, beta=0.8)
        model.update(100.0)
        state = model.update(101.0)
        assert state.actual_duration == pytest.approx(1.0)
        assert state.expected_duration > 0.0
        assert state.surprise > 0.0

    def test_expected_duration(self):
        model = AcdModel(omega=0.1, alpha=0.1, beta=0.8)
        ed = model.expected_duration()
        assert ed > 0.0
        # Unconditional mean = omega / (1 - alpha - beta)
        assert ed == pytest.approx(0.1 / (1.0 - 0.1 - 0.8), abs=1e-6)

    def test_surprise(self):
        model = AcdModel()
        # Before any updates, surprise is 0.0 (no last duration observed)
        assert model.surprise() == pytest.approx(0.0)

    def test_hazard_rate(self):
        model = AcdModel()
        rate = model.hazard_rate(1.0)
        assert rate > 0.0

    def test_hazard_rate_negative_elapsed(self):
        model = AcdModel()
        rate = model.hazard_rate(-1.0)
        assert rate == 0.0

    def test_unusual_detection(self):
        model = AcdModel(omega=1.0, alpha=0.1, beta=0.8)
        model.update(0.0)
        # Very short duration should be unusual (surprise < 0.5)
        state = model.update(0.01)
        # Very long duration should be unusual (surprise > 2.0)
        state2 = model.update(1000.0)
        # At least one should be unusual
        assert state.is_unusual or state2.is_unusual

    def test_state_repr(self):
        model = AcdModel()
        model.update(0.0)
        state = model.update(1.0)
        assert "AcdState" in repr(state)

    def test_nan_event_time(self):
        model = AcdModel()
        with pytest.raises(ValueError):
            model.update(float("nan"))


class TestAcdLogLikelihood:
    def test_basic(self):
        durations = [1.0, 1.2, 0.8, 1.1, 0.9, 1.0, 1.3, 0.7]
        ll = acd_log_likelihood(durations, 0.1, 0.1, 0.8)
        assert ll < 0.0  # Log-likelihood should be negative
        assert ll > float("-inf")

    def test_bad_params(self):
        durations = [1.0, 1.2, 0.8]
        ll = acd_log_likelihood(durations, -1.0, 0.1, 0.8)
        assert ll == float("-inf")

    def test_too_few_durations(self):
        ll = acd_log_likelihood([1.0], 0.1, 0.1, 0.8)
        assert ll == float("-inf")

    def test_non_stationary(self):
        ll = acd_log_likelihood([1.0, 2.0, 3.0], 0.1, 0.5, 0.6)
        assert ll == float("-inf")


class TestFitAcd:
    def test_basic_fit(self):
        # Generate durations from a known ACD process
        durations = [1.0 + 0.1 * (i % 5) for i in range(50)]
        model = fit_acd(durations)
        assert isinstance(model, AcdModel)

    def test_too_few_durations(self):
        with pytest.raises(ValueError):
            fit_acd([1.0, 2.0])

    def test_fitted_model_works(self):
        durations = [0.5, 1.0, 0.8, 1.2, 0.6, 0.9, 1.1, 0.7, 1.0, 0.8]
        model = fit_acd(durations)
        state = model.update(0.0)
        state = model.update(1.0)
        assert state.expected_duration > 0.0
