import unittest
from unittest.mock import MagicMock, patch
import math
from src.theme_nerv_fx import CRTFilter, StatusFlicker, AlertPulsing

class TestThemeNervFx(unittest.TestCase):
 @patch("src.theme_nerv_fx.imgui")
 def test_crt_filter_render(self, mock_imgui):
  # Setup
  mock_draw_list = MagicMock()
  mock_imgui.get_foreground_draw_list.return_value = mock_draw_list
  mock_imgui.get_color_u32.return_value = 0x12345678
  
  overlay = CRTFilter()
  width, height = 800.0, 600.0
  
  # Act
  overlay.render(width, height)
  
  # Assert
  mock_imgui.get_foreground_draw_list.assert_called_once()
  # height is 600, range(0, 600, 2) is 300 calls
  # width is 800, range(0, 800, 3) is 267 calls
  # total = 300 + 267 = 567 calls
  self.assertEqual(mock_draw_list.add_line.call_count, 567)
  # Vignette: v_steps = 20. height=600 is plenty for all insets.
  self.assertEqual(mock_draw_list.add_rect.call_count, 20)
  # Noise: 40 calls to add_rect_filled
  self.assertEqual(mock_draw_list.add_rect_filled.call_count, 40)
  
  # Verify some calls
  mock_draw_list.add_line.assert_any_call((0.0, 0.0), (800.0, 0.0), 0x12345678, 1.2)
  mock_draw_list.add_line.assert_any_call((0.0, 598.0), (800.0, 598.0), 0x12345678, 0.8)

 @patch("src.theme_nerv_fx.imgui")
 def test_crt_filter_disabled(self, mock_imgui):
  mock_draw_list = MagicMock()
  mock_imgui.get_foreground_draw_list.return_value = mock_draw_list
  
  overlay = CRTFilter()
  overlay.enabled = False
  overlay.render(800.0, 600.0)
  
  mock_imgui.get_foreground_draw_list.assert_not_called()

 @patch("src.theme_nerv_fx.time")
 def test_status_flicker_get_alpha(self, mock_time):
  flicker = StatusFlicker()
  
  # Test at multiple points in time to ensure range [0.7, 1.0]
  # sin(0) = 0 -> 0.85
  mock_time.time.return_value = 0.0
  self.assertAlmostEqual(flicker.get_alpha(), 0.85)
  
  # sin(pi/2) = 1 -> 1.0
  mock_time.time.return_value = math.pi / 40.0
  self.assertAlmostEqual(flicker.get_alpha(), 1.0)
  
  # sin(3pi/2) = -1 -> 0.7
  mock_time.time.return_value = 3 * math.pi / 40.0
  self.assertAlmostEqual(flicker.get_alpha(), 0.7)

 def test_alert_pulsing_update(self):
  pulse = AlertPulsing()
  self.assertFalse(pulse.active)
  
  pulse.update("error: something failed")
  self.assertTrue(pulse.active)
  
  pulse.update("Error: alert")
  self.assertTrue(pulse.active)
  
  pulse.update("idle")
  self.assertFalse(pulse.active)

 @patch("src.theme_nerv_fx.imgui")
 @patch("src.theme_nerv_fx.time")
 def test_alert_pulsing_render(self, mock_time, mock_imgui):
  mock_draw_list = MagicMock()
  mock_imgui.get_foreground_draw_list.return_value = mock_draw_list
  mock_imgui.get_color_u32.return_value = 0xFF0000FF
  
  pulse = AlertPulsing()
  pulse.active = True
  
  # sin(0) = 0 -> alpha = 0.05 + 0.15 * (0+1)/2 = 0.125
  mock_time.time.return_value = 0.0
  pulse.render(800.0, 600.0)
  
  mock_imgui.get_foreground_draw_list.assert_called()
  mock_draw_list.add_rect.assert_called_with((0.0, 0.0), (800.0, 600.0), 0xFF0000FF, 0.0, 0, 10.0)

if __name__ == "__main__":
 unittest.main()
