"""
Unit tests for Settings overlay functionality.

Tests the complete workflow of:
1. Opening settings overlay with swipe down gesture
2. Adjusting settings (font size, line spacing, etc.)
3. Live preview updates
4. Closing overlay
"""

import unittest
from pathlib import Path
from dreader import (
    EbookReader,
    TouchEvent,
    GestureType,
    ActionType,
    OverlayState
)


class TestSettingsOverlay(unittest.TestCase):
    """Test Settings overlay opening, interaction, and closing"""

    def setUp(self):
        """Set up test reader with a book"""
        import os
        import zipfile

        self.reader = EbookReader(page_size=(800, 1200))

        # Load a test EPUB - use a larger EPUB for spacing tests
        epub_dir = Path(__file__).parent / 'data' / 'library-epub'
        epubs = list(epub_dir.glob('*.epub'))
        if not epubs:
            self.skipTest("No test EPUB files available")

        # Prefer larger EPUBs for better testing of spacing changes
        # Skip minimal-test.epub as it has too little content
        epubs = [e for e in epubs if 'minimal' not in e.name]
        if not epubs:
            epubs = list(epub_dir.glob('*.epub'))

        test_epub = epubs[0]

        # Debug logging
        print(f"\n=== EPUB Loading Debug Info ===")
        print(f"Test EPUB path: {test_epub}")
        print(f"Absolute path: {test_epub.absolute()}")
        print(f"File exists: {test_epub.exists()}")
        print(f"File size: {test_epub.stat().st_size if test_epub.exists() else 'N/A'}")
        print(f"Is file: {test_epub.is_file() if test_epub.exists() else 'N/A'}")
        print(f"Readable: {os.access(test_epub, os.R_OK) if test_epub.exists() else 'N/A'}")

        # Test if it's a valid ZIP
        if test_epub.exists():
            # Check file magic bytes
            with open(test_epub, 'rb') as f:
                first_bytes = f.read(10)
                print(f"First 10 bytes (hex): {first_bytes.hex()}")
                print(f"First 10 bytes (ascii): {first_bytes[:4]}")
                print(f"Is PK header: {first_bytes[:2] == b'PK'}")

            try:
                with zipfile.ZipFile(test_epub, 'r') as zf:
                    print(f"Valid ZIP: True")
                    print(f"Files in ZIP: {len(zf.namelist())}")
                    print(f"First 3 files: {zf.namelist()[:3]}")
            except Exception as e:
                print(f"ZIP validation error: {e}")

        # Try to load
        success = self.reader.load_epub(str(test_epub))

        if not success:
            print(f"=== Load failed ===")
            # Try loading with pyWebLayout directly for more detailed error
            try:
                from pyWebLayout.io.readers.epub_reader import read_epub
                book = read_epub(str(test_epub))
                print(f"Direct pyWebLayout load: SUCCESS (unexpected!)")
            except Exception as e:
                print(f"Direct pyWebLayout load error: {e}")
                import traceback
                traceback.print_exc()

        self.assertTrue(success, f"Failed to load test EPUB: {test_epub}")

    def tearDown(self):
        """Clean up"""
        self.reader.close()

    def test_open_settings_overlay_directly(self):
        """Test opening settings overlay using direct API call"""
        # Initially no overlay
        self.assertFalse(self.reader.is_overlay_open())

        # Open settings overlay
        overlay_image = self.reader.open_settings_overlay()

        # Should return an image
        self.assertIsNotNone(overlay_image)
        self.assertEqual(overlay_image.size, (800, 1200))

        # Overlay should be open
        self.assertTrue(self.reader.is_overlay_open())
        self.assertEqual(self.reader.get_overlay_state(), OverlayState.SETTINGS)

    def test_close_settings_overlay_directly(self):
        """Test closing settings overlay using direct API call"""
        # Open overlay first
        self.reader.open_settings_overlay()
        self.assertTrue(self.reader.is_overlay_open())

        # Close overlay
        page_image = self.reader.close_overlay()

        # Should return base page
        self.assertIsNotNone(page_image)

        # Overlay should be closed
        self.assertFalse(self.reader.is_overlay_open())
        self.assertEqual(self.reader.get_overlay_state(), OverlayState.NONE)

    def test_swipe_down_from_top_opens_settings(self):
        """Test that swipe down from top of screen opens settings overlay"""
        # Create swipe down event from top of screen (y=100, which is < 20% of 1200)
        event = TouchEvent(
            gesture=GestureType.SWIPE_DOWN,
            x=400,
            y=100
        )

        # Handle gesture
        response = self.reader.handle_touch(event)

        # Should open overlay
        self.assertEqual(response.action, ActionType.OVERLAY_OPENED)
        self.assertEqual(response.data['overlay_type'], 'settings')
        self.assertTrue(self.reader.is_overlay_open())

    def test_swipe_down_from_middle_does_not_open_settings(self):
        """Test that swipe down from middle of screen does NOT open settings"""
        # Create swipe down event from middle of screen (y=600, which is > 20% of 1200)
        event = TouchEvent(
            gesture=GestureType.SWIPE_DOWN,
            x=400,
            y=600
        )

        # Handle gesture
        response = self.reader.handle_touch(event)

        # Should not open overlay
        self.assertEqual(response.action, ActionType.NONE)
        self.assertFalse(self.reader.is_overlay_open())

    def test_tap_outside_closes_settings_overlay(self):
        """Test that tapping outside the settings panel closes it"""
        # Open overlay first
        self.reader.open_settings_overlay()
        self.assertTrue(self.reader.is_overlay_open())

        # Tap in the far left (outside the centered panel)
        event = TouchEvent(
            gesture=GestureType.TAP,
            x=50,  # Well outside panel
            y=600
        )

        # Handle gesture
        response = self.reader.handle_touch(event)

        # Should close overlay
        self.assertEqual(response.action, ActionType.OVERLAY_CLOSED)
        self.assertFalse(self.reader.is_overlay_open())

    def test_font_size_increase(self):
        """Test increasing font size through settings overlay"""
        # Open overlay
        self.reader.open_settings_overlay()
        initial_font_scale = self.reader.base_font_scale

        # Get overlay reader to query button positions from the active overlay sub-application
        overlay_subapp = self.reader._active_overlay
        if not overlay_subapp:
            self.skipTest("No active overlay sub-application")

        overlay_reader = overlay_subapp._overlay_reader

        if not overlay_reader or not overlay_reader.manager:
            self.skipTest("Overlay reader not available for querying")

        # Query the overlay to find the "A+" button link
        # We'll search for it by looking for links with "setting:font_increase"
        page = overlay_reader.manager.get_current_page()

        # Try multiple Y positions in the font size row to find the button
        # Panel is 60% of screen width (480px) centered (x offset = 160)
        # First setting row should be around y=100-150 in panel coordinates
        found_button = False
        tap_x = None
        tap_y = None

        for y in range(80, 180, 10):
            for x in range(300, 450, 20):  # Right side of panel where buttons are
                # Translate to panel coordinates
                panel_x_offset = int((800 - 480) / 2)
                panel_y_offset = int((1200 - 840) / 2)
                panel_x = x - panel_x_offset
                panel_y = y - panel_y_offset

                if panel_x < 0 or panel_y < 0:
                    continue

                result = page.query_point((panel_x, panel_y))
                if result and result.link_target == "setting:font_increase":
                    tap_x = x
                    tap_y = y
                    found_button = True
                    break
            if found_button:
                break

        if not found_button:
            # Fallback: use approximate coordinates
            # Based on HTML layout: panel center + right side button
            tap_x = 550
            tap_y = 350

        # Tap the increase button (in screen coordinates)
        event = TouchEvent(
            gesture=GestureType.TAP,
            x=tap_x,
            y=tap_y
        )

        response = self.reader.handle_touch(event)

        # Should either change setting or close (depending on whether we hit the button)
        if response.action == ActionType.SETTING_CHANGED:
            # Font size should have increased
            self.assertGreater(self.reader.base_font_scale, initial_font_scale)
            # Overlay should still be open
            self.assertTrue(self.reader.is_overlay_open())
        else:
            # If we missed the button, that's OK for this test
            pass

    def test_line_spacing_adjustment(self):
        """Test adjusting line spacing through settings overlay"""
        # Open overlay
        self.reader.open_settings_overlay()
        initial_spacing = self.reader.page_style.line_spacing

        # Close overlay for this test (full interaction would require precise coordinates)
        self.reader.close_overlay()

        # Verify we can adjust line spacing programmatically
        self.reader.set_line_spacing(initial_spacing + 2)
        self.assertEqual(self.reader.page_style.line_spacing, initial_spacing + 2)

    def test_settings_values_displayed_in_overlay(self):
        """Test that current settings values are shown in the overlay"""
        # Set specific values
        self.reader.set_font_size(1.5)  # 150%
        self.reader.set_line_spacing(10)

        # Open overlay
        overlay_image = self.reader.open_settings_overlay()
        self.assertIsNotNone(overlay_image)

        # Overlay should be open with current values
        # (Visual verification would show "150%" and "10px" in the HTML)
        self.assertTrue(self.reader.is_overlay_open())

    def test_multiple_setting_changes(self):
        """Test making multiple setting changes in sequence"""
        initial_font = self.reader.base_font_scale
        initial_spacing = self.reader.page_style.line_spacing

        # Change font size
        self.reader.increase_font_size()
        self.assertNotEqual(self.reader.base_font_scale, initial_font)

        # Change line spacing
        self.reader.set_line_spacing(initial_spacing + 5)
        self.assertNotEqual(self.reader.page_style.line_spacing, initial_spacing)

        # Open overlay to verify values
        overlay_image = self.reader.open_settings_overlay()
        self.assertIsNotNone(overlay_image)

    def test_settings_persist_after_overlay_close(self):
        """Test that setting changes persist after closing overlay"""
        # Make a change
        initial_font = self.reader.base_font_scale
        self.reader.increase_font_size()
        new_font = self.reader.base_font_scale

        # Open and close overlay
        self.reader.open_settings_overlay()
        self.reader.close_overlay()

        # Settings should still be changed
        self.assertEqual(self.reader.base_font_scale, new_font)
        self.assertNotEqual(self.reader.base_font_scale, initial_font)

    def test_overlay_refresh_after_setting_change(self):
        """Test that overlay can be refreshed with updated values"""
        # Open overlay
        self.reader.open_settings_overlay()

        # Access refresh method through active overlay sub-application
        overlay_subapp = self.reader._active_overlay
        if not overlay_subapp:
            self.skipTest("No active overlay sub-application")

        # Change a setting programmatically
        self.reader.increase_font_size()
        new_page = self.reader.get_current_page(include_highlights=False)

        # Refresh overlay
        refreshed_image = overlay_subapp.refresh(
            updated_base_page=new_page,
            font_scale=self.reader.base_font_scale,
            line_spacing=self.reader.page_style.line_spacing,
            inter_block_spacing=self.reader.page_style.inter_block_spacing
        )

        self.assertIsNotNone(refreshed_image)
        self.assertEqual(refreshed_image.size, (800, 1200))

    def test_line_spacing_actually_changes_rendering(self):
        """Verify that line spacing changes produce different rendered images"""
        # Close any open overlay first
        if self.reader.is_overlay_open():
            self.reader.close_overlay()

        # Set initial line spacing and get page
        self.reader.set_line_spacing(5)
        page1 = self.reader.get_current_page()
        self.assertIsNotNone(page1)

        # Change line spacing significantly
        self.reader.set_line_spacing(30)
        page2 = self.reader.get_current_page()
        self.assertIsNotNone(page2)

        # Images should be different (different line spacing should affect rendering)
        self.assertNotEqual(page1.tobytes(), page2.tobytes(),
                           "Line spacing change should affect rendering")

    def test_inter_block_spacing_actually_changes_rendering(self):
        """Verify that inter-block spacing changes produce different rendered images"""
        # Close any open overlay first
        if self.reader.is_overlay_open():
            self.reader.close_overlay()

        # Set initial inter-block spacing and get page
        self.reader.set_inter_block_spacing(15)
        page1 = self.reader.get_current_page()
        self.assertIsNotNone(page1)

        # Change inter-block spacing significantly
        self.reader.set_inter_block_spacing(50)
        page2 = self.reader.get_current_page()
        self.assertIsNotNone(page2)

        # Images should be different
        self.assertNotEqual(page1.tobytes(), page2.tobytes(),
                           "Inter-block spacing change should affect rendering")

    def test_word_spacing_actually_changes_rendering(self):
        """Verify that word spacing changes produce different rendered images"""
        # Close any open overlay first
        if self.reader.is_overlay_open():
            self.reader.close_overlay()

        # Set initial word spacing and get page
        self.reader.set_word_spacing(0)
        page1 = self.reader.get_current_page()
        self.assertIsNotNone(page1)

        # Change word spacing significantly
        self.reader.set_word_spacing(20)
        page2 = self.reader.get_current_page()
        self.assertIsNotNone(page2)

        # Images should be different
        self.assertNotEqual(page1.tobytes(), page2.tobytes(),
                           "Word spacing change should affect rendering")


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