"""
test_camera_health.py — Unit tests for CameraState (no ROS2 required).
"""

import sys
import os
import time
import json

import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

from saltybot_bringup._camera_state import CameraState


class TestCameraState:

    def test_initial_status_never(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        assert cs.status(2.0, 10.0) == 'never'

    def test_age_inf_before_first_frame(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        assert cs.age_s == float('inf')

    def test_on_frame_sets_age_near_zero(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        cs.on_frame()
        assert cs.age_s < 0.1

    def test_on_frame_increments_count(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        for _ in range(5):
            cs.on_frame()
        assert cs.total_frames == 5

    def test_status_ok_after_fresh_frame(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        cs.on_frame()
        assert cs.status(2.0, 10.0) == 'ok'

    def test_status_warn_after_2s(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        cs.on_frame()
        cs.last_frame_t -= 3.0   # fake 3 seconds ago
        assert cs.status(2.0, 10.0) == 'warn'

    def test_status_error_after_10s(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        cs.on_frame()
        cs.last_frame_t -= 11.0
        assert cs.status(2.0, 10.0) == 'error'

    def test_fps_zero_before_frames(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        assert cs.fps(5.0) == pytest.approx(0.0)

    def test_fps_estimate(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        # Simulate 10 frames over 1 second
        base = time.monotonic()
        for i in range(11):
            cs._ts_window.append(base + i * 0.1)
        cs.last_frame_t = base + 1.0
        assert cs.fps(5.0) == pytest.approx(10.0, abs=0.5)

    def test_should_reset_false_for_d435i(self):
        cs = CameraState(topic='/camera/color/image_raw', v4l2_dev=None)
        assert not cs.should_reset()

    def test_should_reset_true_for_csi_first_time(self):
        cs = CameraState(topic='/camera/front/image_raw', v4l2_dev='/dev/video0')
        assert cs.should_reset(cooldown_s=30.0)

    def test_should_reset_false_within_cooldown(self):
        cs = CameraState(topic='/test', v4l2_dev='/dev/video0')
        cs.mark_reset()
        assert not cs.should_reset(cooldown_s=30.0)

    def test_should_reset_true_after_cooldown(self):
        cs = CameraState(topic='/test', v4l2_dev='/dev/video0')
        cs.mark_reset()
        cs._last_reset_t -= 31.0   # fake cooldown elapsed
        assert cs.should_reset(cooldown_s=30.0)

    def test_custom_thresholds(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        cs.on_frame()
        cs.last_frame_t -= 1.5
        assert cs.status(1.0, 5.0) == 'warn'
        assert cs.status(2.0, 5.0) == 'ok'

    def test_fps_ignores_old_frames(self):
        cs = CameraState(topic='/test', v4l2_dev=None)
        old = time.monotonic() - 60.0
        for _ in range(50):
            cs._ts_window.append(old)
        # Recent frames
        now = time.monotonic()
        for i in range(6):
            cs._ts_window.append(now + i * 0.1)
        cs.last_frame_t = now + 0.5
        assert cs.fps(5.0) == pytest.approx(10.0, abs=1.0)


if __name__ == '__main__':
    pytest.main([__file__, '-v'])
