"""
Comprehensive tests for boot recovery and resume functionality.

Tests cover:
- Saving state when closing reader
- Resuming from saved state with a new reader instance
- Restoring reading position (page/chapter)
- Restoring settings (font size, spacing, etc.)
- Restoring bookmarks
- Handling state across multiple books
- Error recovery (corrupt state, missing books)
- Bookmark-based position restoration
"""

import unittest
import tempfile
import shutil
import json
import asyncio
from pathlib import Path
from typing import Dict, Any

from dreader.application import EbookReader
from dreader.state import StateManager, AppState, BookState, Settings, EreaderMode, OverlayState


class TestBootRecovery(unittest.TestCase):
    """Test application state persistence and recovery across reader instances"""

    def setUp(self):
        """Set up test environment with temporary directories"""
        self.temp_dir = tempfile.mkdtemp()
        self.bookmarks_dir = Path(self.temp_dir) / "bookmarks"
        self.highlights_dir = Path(self.temp_dir) / "highlights"
        self.state_file = Path(self.temp_dir) / "state.json"

        self.bookmarks_dir.mkdir(exist_ok=True)
        self.highlights_dir.mkdir(exist_ok=True)

        self.epub_path = "tests/data/test.epub"

        if not Path(self.epub_path).exists():
            self.skipTest(f"Test EPUB not found at {self.epub_path}")

    def tearDown(self):
        """Clean up test environment"""
        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_save_and_restore_reading_position(self):
        """Test saving current position and restoring it in a new reader"""
        # Create first reader instance
        reader1 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )

        # Load book and navigate to middle
        reader1.load_epub(self.epub_path)

        # Navigate forward several pages
        for _ in range(5):
            reader1.next_page()

        # Get position before saving
        original_position = reader1.get_position_info()
        original_progress = reader1.get_reading_progress()

        # Save position using special auto-resume bookmark
        reader1.save_position("__auto_resume__")

        # Close reader
        reader1.close()

        # Create new reader instance
        reader2 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )

        # Load same book
        reader2.load_epub(self.epub_path)

        # Restore position
        success = reader2.load_position("__auto_resume__")

        self.assertTrue(success, "Failed to load auto-resume position")

        # Verify position matches
        restored_position = reader2.get_position_info()
        restored_progress = reader2.get_reading_progress()

        # Compare positions using the position dict
        self.assertEqual(original_position.get('position'), restored_position.get('position'),
                        f"Position mismatch: {original_position} vs {restored_position}")
        self.assertAlmostEqual(original_progress, restored_progress,
                              places=2, msg="Progress percentage mismatch")

        reader2.close()

    def test_save_and_restore_settings(self):
        """Test saving settings and restoring them in a new reader"""
        # Create first reader
        reader1 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader1.load_epub(self.epub_path)

        # Change settings
        reader1.increase_font_size()
        reader1.increase_font_size()
        reader1.set_line_spacing(10)
        reader1.set_inter_block_spacing(25)

        # Get settings
        original_font_scale = reader1.base_font_scale
        original_line_spacing = reader1.page_style.line_spacing
        original_inter_block = reader1.page_style.inter_block_spacing

        # Create state manager and save settings
        state_manager = StateManager(str(self.state_file), auto_save_interval=999)
        state_manager.update_settings({
            'font_scale': original_font_scale,
            'line_spacing': original_line_spacing,
            'inter_block_spacing': original_inter_block
        })
        state_manager.save_state(force=True)

        reader1.close()

        # Create new reader
        reader2 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader2.load_epub(self.epub_path)

        # Load state and apply settings
        state_manager2 = StateManager(str(self.state_file), auto_save_interval=999)
        state_manager2.load_state()
        settings_dict = state_manager2.get_settings().to_dict()

        reader2.apply_settings(settings_dict)

        # Verify settings match
        self.assertAlmostEqual(original_font_scale, reader2.base_font_scale, places=2,
                              msg="Font scale mismatch")
        self.assertEqual(original_line_spacing, reader2.page_style.line_spacing,
                        "Line spacing mismatch")
        self.assertEqual(original_inter_block, reader2.page_style.inter_block_spacing,
                        "Inter-block spacing mismatch")

        reader2.close()

    def test_save_and_restore_bookmarks(self):
        """Test that bookmarks persist across reader instances"""
        # Create first reader
        reader1 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader1.load_epub(self.epub_path)

        # Navigate and create bookmarks
        reader1.next_page()
        reader1.next_page()
        reader1.save_position("bookmark1")

        reader1.next_page()
        reader1.next_page()
        reader1.next_page()
        reader1.save_position("bookmark2")

        # Get bookmark list
        original_bookmarks = reader1.list_saved_positions()
        self.assertGreater(len(original_bookmarks), 0, "No bookmarks saved")

        reader1.close()

        # Create new reader
        reader2 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader2.load_epub(self.epub_path)

        # Check bookmarks exist
        restored_bookmarks = reader2.list_saved_positions()

        self.assertIn("bookmark1", restored_bookmarks, "bookmark1 not found")
        self.assertIn("bookmark2", restored_bookmarks, "bookmark2 not found")

        # Test loading each bookmark
        success1 = reader2.load_position("bookmark1")
        self.assertTrue(success1, "Failed to load bookmark1")

        success2 = reader2.load_position("bookmark2")
        self.assertTrue(success2, "Failed to load bookmark2")

        reader2.close()

    def test_full_state_persistence_workflow(self):
        """Test complete workflow: read, change settings, save, close, restore"""
        # Session 1: Initial reading session
        reader1 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader1.load_epub(self.epub_path)

        # Simulate reading session
        for _ in range(3):
            reader1.next_page()

        reader1.increase_font_size()
        reader1.set_line_spacing(8)

        # Save everything
        reader1.save_position("__auto_resume__")
        reader1.save_position("my_bookmark")

        session1_position = reader1.get_position_info()
        session1_progress = reader1.get_reading_progress()
        session1_font = reader1.base_font_scale
        session1_spacing = reader1.page_style.line_spacing

        # Save state
        state_manager = StateManager(str(self.state_file), auto_save_interval=999)
        state_manager.set_current_book(BookState(
            path=self.epub_path,
            title=reader1.book_title or "Test Book",
            author=reader1.book_author or "Test Author"
        ))
        state_manager.update_settings({
            'font_scale': session1_font,
            'line_spacing': session1_spacing
        })
        state_manager.save_state(force=True)

        reader1.close()

        # Session 2: Resume reading
        state_manager2 = StateManager(str(self.state_file), auto_save_interval=999)
        loaded_state = state_manager2.load_state()

        # Verify state loaded
        self.assertIsNotNone(loaded_state.current_book, "No current book in state")
        self.assertEqual(loaded_state.current_book.path, self.epub_path,
                        "Book path mismatch")

        # Create new reader and restore
        reader2 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader2.load_epub(loaded_state.current_book.path)
        reader2.apply_settings(loaded_state.settings.to_dict())
        reader2.load_position("__auto_resume__")

        # Verify restoration
        session2_position = reader2.get_position_info()
        session2_progress = reader2.get_reading_progress()

        self.assertEqual(session1_position.get('position'), session2_position.get('position'),
                        "Position not restored correctly")
        self.assertAlmostEqual(session1_progress, session2_progress, places=2,
                              msg="Progress not restored correctly")
        self.assertAlmostEqual(session1_font, reader2.base_font_scale, places=2,
                              msg="Font scale not restored correctly")
        self.assertEqual(session1_spacing, reader2.page_style.line_spacing,
                        "Line spacing not restored correctly")

        # Verify bookmark exists
        bookmarks = reader2.list_saved_positions()
        self.assertIn("my_bookmark", bookmarks, "Bookmark lost after restart")

        reader2.close()

    def test_multiple_books_separate_state(self):
        """Test that different books maintain separate positions and bookmarks"""
        epub_path = self.epub_path

        # Book 1 - First session
        reader1 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader1.load_epub(epub_path)

        for _ in range(3):
            reader1.next_page()

        reader1.save_position("__auto_resume__")
        book1_position = reader1.get_position_info()
        book1_progress = reader1.get_reading_progress()
        book1_doc_id = reader1.document_id

        reader1.close()

        # Book 1 - Second session (simulate reopening)
        reader2 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader2.load_epub(epub_path)
        reader2.load_position("__auto_resume__")

        # Verify we're at the same position
        book1_position_restored = reader2.get_position_info()
        book1_progress_restored = reader2.get_reading_progress()

        self.assertEqual(book1_position.get('position'), book1_position_restored.get('position'),
                        "Book position not preserved across sessions")
        self.assertAlmostEqual(book1_progress, book1_progress_restored, places=2,
                              msg="Book progress not preserved")

        # Now navigate further and save again
        for _ in range(2):
            reader2.next_page()

        reader2.save_position("__auto_resume__")
        book1_position_updated = reader2.get_position_info()
        book1_progress_updated = reader2.get_reading_progress()

        reader2.close()

        # Book 1 - Third session, verify updated position
        reader3 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader3.load_epub(epub_path)
        reader3.load_position("__auto_resume__")

        book1_position_final = reader3.get_position_info()
        book1_progress_final = reader3.get_reading_progress()

        self.assertEqual(book1_position_updated.get('position'), book1_position_final.get('position'),
                        "Updated position not preserved")
        self.assertAlmostEqual(book1_progress_updated, book1_progress_final, places=2,
                              msg="Updated progress not preserved")

        reader3.close()

    def test_corrupt_state_file_recovery(self):
        """Test graceful handling of corrupt state file"""
        # Create corrupt state file
        with open(self.state_file, 'w') as f:
            f.write("{ corrupt json content ][[ }")

        # Try to load state
        state_manager = StateManager(str(self.state_file), auto_save_interval=999)
        state = state_manager.load_state()

        # Should return default state, not crash
        self.assertIsNotNone(state)
        self.assertEqual(state.mode, EreaderMode.LIBRARY)
        self.assertIsNone(state.current_book)

        # Verify backup was created
        backup_file = self.state_file.with_suffix('.json.backup')
        self.assertTrue(backup_file.exists(), "Backup file not created for corrupt state")

    def test_missing_book_in_state(self):
        """Test handling when saved state references a missing book"""
        # Create valid state pointing to non-existent book
        state_manager = StateManager(str(self.state_file), auto_save_interval=999)
        state_manager.set_current_book(BookState(
            path="/nonexistent/book.epub",
            title="Missing Book",
            author="Ghost Author"
        ))
        state_manager.save_state(force=True)

        # Load state
        state_manager2 = StateManager(str(self.state_file), auto_save_interval=999)
        state = state_manager2.load_state()

        # State loads successfully
        self.assertIsNotNone(state.current_book)
        self.assertEqual(state.current_book.path, "/nonexistent/book.epub")

        # But trying to load the book should fail gracefully
        reader = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        success = reader.load_epub(state.current_book.path)

        self.assertFalse(success, "Should fail to load non-existent book")
        self.assertFalse(reader.is_loaded(), "Reader should not be in loaded state")

        reader.close()

    def test_no_state_file_cold_start(self):
        """Test first boot with no existing state file"""
        # Ensure no state file exists
        if self.state_file.exists():
            self.state_file.unlink()

        # Create state manager
        state_manager = StateManager(str(self.state_file), auto_save_interval=999)
        state = state_manager.load_state()

        # Should get default state
        self.assertEqual(state.mode, EreaderMode.LIBRARY)
        self.assertIsNone(state.current_book)
        self.assertEqual(state.overlay, OverlayState.NONE)
        self.assertEqual(state.settings.font_scale, 1.0)

        # Should be able to save new state
        success = state_manager.save_state(force=True)
        self.assertTrue(success, "Failed to save initial state")
        self.assertTrue(self.state_file.exists(), "State file not created")

    def test_position_survives_settings_change(self):
        """Test that position is preserved when settings change"""
        # Create reader and navigate
        reader1 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader1.load_epub(self.epub_path)

        # Navigate to specific position
        for _ in range(4):
            reader1.next_page()

        reader1.save_position("__auto_resume__")
        position1_info = reader1.get_position_info()

        # Change font size (which re-paginates)
        reader1.increase_font_size()
        reader1.increase_font_size()

        # Position might change due to repagination, but logical position is preserved
        # Save again
        reader1.save_position("__auto_resume__")
        position_after_resize_info = reader1.get_position_info()
        position_after_resize_progress = reader1.get_reading_progress()

        reader1.close()

        # Create new reader with same settings
        reader2 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader2.load_epub(self.epub_path)

        # Apply same font size
        reader2.increase_font_size()
        reader2.increase_font_size()

        # Load position
        reader2.load_position("__auto_resume__")
        position2_info = reader2.get_position_info()
        position2_progress = reader2.get_reading_progress()

        # Should match the position after resize, not the original
        self.assertEqual(position_after_resize_info.get('position'), position2_info.get('position'),
                        "Position not preserved after font size change")
        self.assertAlmostEqual(position_after_resize_progress, position2_progress, places=2,
                              msg="Progress not preserved after font size change")

        reader2.close()

    def test_chapter_position_restoration(self):
        """Test that chapter context is preserved across sessions"""
        # Create reader and jump to specific chapter
        reader1 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader1.load_epub(self.epub_path)

        # Get chapters
        chapters = reader1.get_chapters()
        if len(chapters) < 2:
            self.skipTest("Test EPUB needs at least 2 chapters")

        # Jump to second chapter
        _, chapter_idx = chapters[1]
        reader1.jump_to_chapter(chapter_idx)

        # Navigate a bit within the chapter
        reader1.next_page()

        # Save position
        reader1.save_position("__auto_resume__")
        chapter1_position = reader1.get_position_info()
        chapter1_progress = reader1.get_reading_progress()

        reader1.close()

        # Create new reader and restore
        reader2 = EbookReader(
            bookmarks_dir=str(self.bookmarks_dir),
            highlights_dir=str(self.highlights_dir)
        )
        reader2.load_epub(self.epub_path)
        reader2.load_position("__auto_resume__")

        # Verify we're at the right position
        chapter2_position = reader2.get_position_info()
        chapter2_progress = reader2.get_reading_progress()

        self.assertEqual(chapter1_position.get('position'), chapter2_position.get('position'),
                        "Chapter position not restored correctly")
        self.assertAlmostEqual(chapter1_progress, chapter2_progress, places=2,
                              msg="Chapter progress not restored correctly")

        reader2.close()


class TestStateManagerAsync(unittest.TestCase):
    """Test StateManager async functionality"""

    def setUp(self):
        """Set up test environment"""
        self.temp_dir = tempfile.mkdtemp()
        self.state_file = Path(self.temp_dir) / "state.json"

    def tearDown(self):
        """Clean up test environment"""
        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def test_async_auto_save(self):
        """Test that async auto-save works"""
        async def test_auto_save():
            # Create state manager with short interval
            state_manager = StateManager(str(self.state_file), auto_save_interval=1)

            # Start auto-save
            state_manager.start_auto_save()

            # Make a change
            state_manager.set_mode(EreaderMode.READING)

            # Wait for auto-save to trigger
            await asyncio.sleep(1.5)

            # Stop auto-save
            await state_manager.stop_auto_save(save_final=True)

            # Verify file was saved
            self.assertTrue(self.state_file.exists(), "State file not created")

            # Load and verify
            with open(self.state_file) as f:
                data = json.load(f)

            self.assertEqual(data['mode'], 'reading')

        # Run async test
        asyncio.run(test_auto_save())

    def test_async_save_with_lock(self):
        """Test that async saves are thread-safe"""
        async def test_concurrent_saves():
            state_manager = StateManager(str(self.state_file), auto_save_interval=999)

            # Make multiple concurrent saves
            tasks = []
            for i in range(10):
                state_manager.update_setting('brightness', i)
                tasks.append(state_manager.save_state_async(force=True))

            # Wait for all saves
            results = await asyncio.gather(*tasks)

            # All should succeed
            self.assertTrue(all(results), "Some saves failed")

            # File should exist and be valid
            self.assertTrue(self.state_file.exists())

            # Load and verify (should have last value)
            with open(self.state_file) as f:
                data = json.load(f)

            # Brightness should be set (exact value depends on race, but should be 0-9)
            self.assertIn(data['settings']['brightness'], range(10))

        asyncio.run(test_concurrent_saves())


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