"""
GUI integration testing for rateloader.

Tests GUI functionality and integration with backend components.
Validates GUI responsiveness, functionality, and backend communication.
"""

import os
import sys
import time
import tempfile
import shutil
import unittest
from unittest.mock import Mock, patch, MagicMock
import tkinter as tk
from tkinter import ttk

# Add current directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from gui.app import RateLoaderGUI
from core.config import get_config, reload_config
from core.task_queue import get_task_queue, enqueue_task, get_queue_status
from core.scheduler import get_scheduler, get_scheduler_status
from core.http_server import get_http_server, start_server, stop_server, is_server_running
from core.tasks import ExportMailerTask, PriceExportTask


class TestGUIIntegration(unittest.TestCase):
    """Integration tests for GUI functionality."""

    @classmethod
    def setUpClass(cls):
        """Set up test environment."""
        # Create temporary directory for test files
        cls.test_dir = tempfile.mkdtemp()
        cls.original_dir = os.getcwd()
        os.chdir(cls.test_dir)
        
        # Create log directory
        os.makedirs("log", exist_ok=True)
        
        # Backup original settings.ini if exists
        cls.settings_backup = None
        if os.path.exists("settings.ini"):
            cls.settings_backup = "settings.ini.backup"
            shutil.copy("settings.ini", cls.settings_backup)
        
        # Initialize test configuration
        cls._setup_test_config()
        
        print(f"GUI test environment set up in: {cls.test_dir}")

    @classmethod
    def tearDownClass(cls):
        """Clean up test environment."""
        # Restore original directory
        os.chdir(cls.original_dir)
        
        # Clean up test directory
        shutil.rmtree(cls.test_dir, ignore_errors=True)
        
        print(f"GUI test environment cleaned up: {cls.test_dir}")

    def setUp(self):
        """Set up for each test."""
        # Reload configuration
        reload_config()
        
        # Mock backend components to avoid actual network/database calls
        self._setup_mocks()

    def tearDown(self):
        """Clean up after each test."""
        # Close GUI if it exists
        if hasattr(self, 'gui') and self.gui:
            try:
                self.gui.root.destroy()
            except:
                pass

    @classmethod
    def _setup_test_config(cls):
        """Set up test configuration."""
        config = get_config()
        
        # Test configuration values
        test_config = {
            "oracleconnectstring": "test/test@test",
            "nls_lang": "AMERICAN_AMERICA.CL8MSWIN1251",
            "file_encoding": "utf-8",
            "url": "http://test.com/select.sql",
            "block-url": "http://test.com/selectBlocks.sql",
            "sendfrom": "test@example.com",
            "sendto": "recipient@example.com",
            "mailserver": "smtp.test.com",
            "port": "587",
            "pwd": "testpass",
            "ssl": "1",
            "api_url": "http://test.com/api/prices",
            "token": "test-token",
            "months": "1",
            "server_port": "8001",
            "schedule_hours": "9,15,21"
        }
        
        for key, value in test_config.items():
            config.set(key, value)

    def _setup_mocks(self):
        """Set up mocks for backend components."""
        # Mock task queue
        self.mock_task_queue = Mock()
        self.mock_task_queue.is_running.return_value = True
        self.mock_task_queue.start.return_value = True
        self.mock_task_queue.stop.return_value = True
        self.mock_task_queue.enqueue.return_value = True
        self.mock_task_queue.get_status.return_value = {
            "worker_running": True,
            "queue_length": 0,
            "current_task": None,
            "statistics": {"total_enqueued": 0, "total_completed": 0, "total_failed": 0}
        }

        # Mock scheduler
        self.mock_scheduler = Mock()
        self.mock_scheduler.is_running.return_value = True
        self.mock_scheduler.start.return_value = True
        self.mock_scheduler.stop.return_value = True
        self.mock_scheduler.get_status.return_value = {
            "running": True,
            "schedule_hours": [9, 15, 21],
            "next_scheduled_time": None,
            "thread_alive": True
        }

        # Mock HTTP server
        self.mock_http_server = Mock()
        self.mock_http_server.is_running = False
        self.mock_http_server.start.return_value = True
        self.mock_http_server.stop.return_value = True

        # Mock logger manager
        self.mock_logger_manager = Mock()

    def _create_gui(self):
        """Create GUI instance with mocked components."""
        with patch('gui.app.get_task_queue', return_value=self.mock_task_queue):
            with patch('gui.app.get_scheduler', return_value=self.mock_scheduler):
                with patch('gui.app.get_http_server', return_value=self.mock_http_server):
                    with patch('gui.app.get_logger_manager', return_value=self.mock_logger_manager):
                        with patch('gui.app.get_config'):
                            self.gui = RateLoaderGUI()
                            return self.gui

    def test_gui_initialization(self):
        """Test GUI initializes correctly."""
        gui = self._create_gui()
        
        # Verify main window
        self.assertIsNotNone(gui.root)
        self.assertEqual(gui.root.title(), "RateLoader v3.0")
        
        # Verify tabs are created
        self.assertIsNotNone(gui.notebook)
        tabs = gui.notebook.tabs()
        self.assertGreater(len(tabs), 0)
        
        # Verify status bar
        self.assertIsNotNone(gui.status_label)
        self.assertIsNotNone(gui.time_label)

    def test_settings_tab_functionality(self):
        """Test Settings tab functionality."""
        gui = self._create_gui()
        
        # Find Settings tab
        settings_frame = None
        for tab in gui.notebook.tabs():
            frame = gui.notebook.nametowidget(tab)
            if hasattr(frame, 'settings_vars'):
                settings_frame = frame
                break
        
        self.assertIsNotNone(settings_frame)
        
        # Verify settings variables are created
        self.assertIn("oracleconnectstring", settings_frame.settings_vars)
        self.assertIn("server_port", settings_frame.settings_vars)
        
        # Test setting values
        settings_frame.settings_vars["oracleconnectstring"].set("test/value@test")
        
        # Test save functionality (mocked)
        with patch.object(gui.config, 'set') as mock_set:
            with patch('tkinter.messagebox.showinfo'):
                gui._save_settings()
                
                # Verify config.set was called
                mock_set.assert_called()

    def test_export_mailer_tab_functionality(self):
        """Test Export Mailer tab functionality."""
        gui = self._create_gui()
        
        # Find Export Mailer tab
        export_frame = None
        for tab in gui.notebook.tabs():
            frame = gui.notebook.nametowidget(tab)
            if hasattr(frame, 'export_mailer_status'):
                export_frame = frame
                break
        
        self.assertIsNotNone(export_frame)
        
        # Verify components exist
        self.assertIsNotNone(export_frame.export_mailer_status)
        self.assertIsNotNone(export_frame.export_mailer_progress)
        self.assertIsNotNone(export_frame.export_mailer_last_run)
        
        # Test run button functionality
        with patch('gui.app.enqueue_task', return_value=True) as mock_enqueue:
            gui._run_export_mailer()
            
            # Verify task was enqueued
            mock_enqueue.assert_called_once()
            args = mock_enqueue.call_args[0]
            self.assertIsInstance(args[0], ExportMailerTask)

    def test_price_export_tab_functionality(self):
        """Test Price Export tab functionality."""
        gui = self._create_gui()
        
        # Find Price Export tab
        price_frame = None
        for tab in gui.notebook.tabs():
            frame = gui.notebook.nametowidget(tab)
            if hasattr(frame, 'price_export_status'):
                price_frame = frame
                break
        
        self.assertIsNotNone(price_frame)
        
        # Verify components exist
        self.assertIsNotNone(price_frame.price_export_status)
        self.assertIsNotNone(price_frame.price_export_progress)
        self.assertIsNotNone(price_frame.price_export_last_run)
        
        # Test run button functionality
        with patch('gui.app.enqueue_task', return_value=True) as mock_enqueue:
            gui._run_price_export()
            
            # Verify task was enqueued
            mock_enqueue.assert_called_once()
            args = mock_enqueue.call_args[0]
            self.assertIsInstance(args[0], PriceExportTask)

    def test_rate_loader_tab_functionality(self):
        """Test Rate Loader tab functionality."""
        gui = self._create_gui()
        
        # Find Rate Loader tab
        rate_frame = None
        for tab in gui.notebook.tabs():
            frame = gui.notebook.nametowidget(tab)
            if hasattr(frame, 'rate_loader_status'):
                rate_frame = frame
                break
        
        self.assertIsNotNone(rate_frame)
        
        # Verify components exist
        self.assertIsNotNone(rate_frame.start_server_btn)
        self.assertIsNotNone(rate_frame.stop_server_btn)
        self.assertIsNotNone(rate_frame.rate_loader_status)
        
        # Test server start functionality
        with patch('gui.app.start_server', return_value=True) as mock_start:
            gui._start_server()
            mock_start.assert_called_once()
        
        # Test server stop functionality
        with patch('gui.app.stop_server', return_value=True) as mock_stop:
            gui._stop_server()
            mock_stop.assert_called_once()

    def test_logs_tab_functionality(self):
        """Test Logs tab functionality."""
        gui = self._create_gui()
        
        # Find Logs tab
        logs_frame = None
        for tab in gui.notebook.tabs():
            frame = gui.notebook.nametowidget(tab)
            if hasattr(frame, 'log_display'):
                logs_frame = frame
                break
        
        self.assertIsNotNone(logs_frame)
        
        # Verify components exist
        self.assertIsNotNone(logs_frame.log_display)
        self.assertIsNotNone(logs_frame.log_file_combo)
        self.assertIsNotNone(logs_frame.auto_refresh_enabled)
        
        # Test log file selection
        log_files = ["hotellabexport.log", "hotellabpriceexport.log", "rateloader.log"]
        self.assertEqual(logs_frame.log_file_combo['values'], log_files)
        
        # Test refresh functionality
        with patch.object(gui, '_refresh_logs') as mock_refresh:
            gui._refresh_logs()
            mock_refresh.assert_called_once()

    def test_status_bar_updates(self):
        """Test status bar updates correctly."""
        gui = self._create_gui()
        
        # Mock status functions
        with patch('gui.app.get_queue_status', return_value={"queue_length": 2}):
            with patch('gui.app.get_scheduler_status', return_value={"running": True}):
                with patch('gui.app.is_server_running', return_value=False):
                    # Update status
                    gui._update_status()
                    
                    # Verify status text was updated
                    status_text = gui.status_label.cget("text")
                    self.assertIn("Queue: 2 tasks", status_text)
                    self.assertIn("Scheduler: Running", status_text)
                    self.assertIn("Server: Stopped", status_text)

    def test_time_display_updates(self):
        """Test time display updates correctly."""
        gui = self._create_gui()
        
        # Update time
        gui._update_time()
        
        # Verify time was updated
        time_text = gui.time_label.cget("text")
        self.assertRegex(time_text, r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}")

    def test_gui_responsiveness(self):
        """Test GUI remains responsive during operations."""
        gui = self._create_gui()
        
        # Simulate long-running operation
        def long_operation():
            time.sleep(0.1)  # Simulate work
            return True
        
        # Test that GUI doesn't freeze during operation
        with patch('gui.app.enqueue_task', return_value=True):
            start_time = time.time()
            gui._run_export_mailer()
            end_time = time.time()
            
            # Operation should complete quickly (GUI shouldn't block)
            self.assertLess(end_time - start_time, 0.5)

    def test_error_handling_in_gui(self):
        """Test GUI handles errors gracefully."""
        gui = self._create_gui()
        
        # Test error in task enqueue
        with patch('gui.app.enqueue_task', side_effect=Exception("Test error")):
            with patch('gui.app.messagebox.showerror') as mock_error:
                gui._run_export_mailer()
                mock_error.assert_called_once()
        
        # Test error in settings save
        with patch.object(gui.config, 'set', side_effect=Exception("Config error")):
            with patch('gui.app.messagebox.showerror') as mock_error:
                gui._save_settings()
                mock_error.assert_called_once()

    def test_gui_backend_communication(self):
        """Test GUI communicates correctly with backend components."""
        gui = self._create_gui()
        
        # Test task queue communication
        with patch('gui.app.get_queue_status') as mock_status:
            mock_status.return_value = {"queue_length": 1, "worker_running": True}
            gui._update_status()
            mock_status.assert_called_once()
        
        # Test scheduler communication
        with patch('gui.app.get_scheduler_status') as mock_status:
            mock_status.return_value = {"running": True, "schedule_hours": [9, 15, 21]}
            gui._update_status()
            mock_status.assert_called_once()
        
        # Test HTTP server communication
        with patch('gui.app.is_server_running', return_value=True):
            gui._update_status()
            # Verify server buttons are updated correctly
            self.assertEqual(gui.start_server_btn['state'], tk.DISABLED)
            self.assertEqual(gui.stop_server_btn['state'], tk.NORMAL)

    def test_configuration_reload_in_gui(self):
        """Test configuration reload updates GUI."""
        gui = self._create_gui()
        
        # Change configuration value
        original_value = gui.settings_vars.get("server_port").get()
        gui.settings_vars["server_port"].set("9999")
        
        # Mock reload
        with patch('gui.app.reload_config') as mock_reload:
            with patch('gui.app.get_config') as mock_get_config:
                mock_config = Mock()
                mock_config.get.return_value = "8000"
                mock_get_config.return_value = mock_config
                
                with patch('tkinter.messagebox.showinfo'):
                    gui._reload_settings()
                    
                    # Verify reload was called
                    mock_reload.assert_called_once()
                    
                    # Verify GUI was updated
                    self.assertEqual(gui.settings_vars["server_port"].get(), "8000")

    def test_log_display_functionality(self):
        """Test log display functionality."""
        gui = self._create_gui()
        
        # Create test log file
        os.makedirs("log", exist_ok=True)
        test_log_path = "log/test.log"
        with open(test_log_path, 'w') as f:
            f.write("Test log line 1\n")
            f.write("Test log line 2\n")
            f.write("Test log line 3\n")
        
        # Set log file selection
        gui.selected_log_file.set("test.log")
        
        # Refresh logs
        gui._refresh_logs()
        
        # Verify log content is displayed
        log_content = gui.log_display.get(1.0, tk.END)
        self.assertIn("Test log line 1", log_content)
        self.assertIn("Test log line 2", log_content)
        self.assertIn("Test log line 3", log_content)
        
        # Clean up
        os.remove(test_log_path)

    def test_auto_refresh_functionality(self):
        """Test auto-refresh functionality."""
        gui = self._create_gui()
        
        # Enable auto-refresh
        gui.auto_refresh_enabled.set(True)
        
        # Mock refresh function
        with patch.object(gui, '_refresh_logs') as mock_refresh:
            gui._refresh_logs_if_enabled()
            mock_refresh.assert_called_once()
        
        # Disable auto-refresh
        gui.auto_refresh_enabled.set(False)
        
        with patch.object(gui, '_refresh_logs') as mock_refresh:
            gui._refresh_logs_if_enabled()
            mock_refresh.assert_not_called()

    def test_gui_shutdown_procedure(self):
        """Test GUI shutdown procedure."""
        gui = self._create_gui()
        
        # Test shutdown
        with patch.object(gui.task_queue, 'stop') as mock_queue_stop:
            with patch.object(gui.scheduler, 'stop') as mock_scheduler_stop:
                with patch.object(gui.http_server, 'stop') as mock_server_stop:
                    gui._on_closing()
                    
                    # Verify all components were stopped
                    mock_queue_stop.assert_called_once()
                    mock_scheduler_stop.assert_called_once()
                    mock_server_stop.assert_called_once()
                    
                    # Verify GUI is marked as closing
                    self.assertTrue(gui.is_closing)

    def test_menu_functionality(self):
        """Test menu functionality."""
        gui = self._create_gui()
        
        # Test About dialog
        with patch('tkinter.messagebox.showinfo') as mock_about:
            gui._show_about()
            mock_about.assert_called_once()
            args = mock_about.call_args[0]
            self.assertIn("RateLoader v3.0", args[1])

    def test_task_status_logging(self):
        """Test task status logging in GUI."""
        gui = self._create_gui()
        
        # Test Export Mailer logging
        gui._log_to_export_mailer("Test message")
        content = gui.export_mailer_status.get(1.0, tk.END)
        self.assertIn("Test message", content)
        
        # Test Price Export logging
        gui._log_to_price_export("Test message")
        content = gui.price_export_status.get(1.0, tk.END)
        self.assertIn("Test message", content)
        
        # Test Rate Loader logging
        gui._log_to_rate_loader("Test message")
        content = gui.rate_loader_status.get(1.0, tk.END)
        self.assertIn("Test message", content)

    def test_progress_indicators(self):
        """Test progress indicators work correctly."""
        gui = self._create_gui()
        
        # Test Export Mailer progress
        gui.export_mailer_progress.start()
        time.sleep(0.1)  # Let it run briefly
        gui.export_mailer_progress.stop()
        
        # Test Price Export progress
        gui.price_export_progress.start()
        time.sleep(0.1)  # Let it run briefly
        gui.price_export_progress.stop()

    def test_server_button_states(self):
        """Test server button states update correctly."""
        gui = self._create_gui()
        
        # Test server running state
        with patch('gui.app.is_server_running', return_value=True):
            gui._update_status()
            self.assertEqual(gui.start_server_btn['state'], tk.DISABLED)
            self.assertEqual(gui.stop_server_btn['state'], tk.NORMAL)
        
        # Test server stopped state
        with patch('gui.app.is_server_running', return_value=False):
            gui._update_status()
            self.assertEqual(gui.start_server_btn['state'], tk.NORMAL)
            self.assertEqual(gui.stop_server_btn['state'], tk.DISABLED)


def run_gui_integration_tests():
    """Run all GUI integration tests."""
    print("Running GUI integration tests for rateloader...")
    print("=" * 60)
    
    # Create test suite
    suite = unittest.TestLoader().loadTestsFromTestCase(TestGUIIntegration)
    
    # Run tests
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    # Print summary
    print("=" * 60)
    print(f"Tests run: {result.testsRun}")
    print(f"Failures: {len(result.failures)}")
    print(f"Errors: {len(result.errors)}")
    print(f"Skipped: {len(result.skipped)}")
    
    if result.failures:
        print("\nFailures:")
        for test, traceback in result.failures:
            print(f"  - {test}: {traceback}")
    
    if result.errors:
        print("\nErrors:")
        for test, traceback in result.errors:
            print(f"  - {test}: {traceback}")
    
    success = len(result.failures) == 0 and len(result.errors) == 0
    print(f"\nOverall result: {'PASS' if success else 'FAIL'}")
    
    return success


if __name__ == "__main__":
    success = run_gui_integration_tests()
    sys.exit(0 if success else 1)