#!/usr/bin/env python3
"""
Browser automation tests using Selenium.
Tests run against the device in an actual browser. Target host defaults to
``127.0.0.1:5000``; override with ``LED_CONTROLLER_DEVICE_IP`` (IP or hostname,
or a full ``http://`` / ``https://`` base URL).

Fixed delays between UI steps use ``LED_CONTROLLER_BROWSER_SLEEP_SCALE``
(default ``0.5``, i.e. half the nominal pause). Set to ``1`` for the old pacing,
or ``0`` to skip fixed sleeps (may flake). Driver implicit wait defaults to
``2`` seconds; override with ``LED_CONTROLLER_BROWSER_IMPLICIT_WAIT``.

On Pi OS Lite (no desktop) these tests are skipped unless headless Chromium
and chromedriver are installed (e.g. chromium-browser chromium-chromedriver).
"""

import os
import sys

import pytest

if os.environ.get("LED_CONTROLLER_RUN_BROWSER_TESTS") != "1":
    # pytest catches Skipped; plain `python tests/test_browser.py` does not.
    if __name__ == "__main__":
        print(
            "Browser tests are disabled by default. "
            "Set LED_CONTROLLER_RUN_BROWSER_TESTS=1 to run.",
            file=sys.stderr,
        )
        raise SystemExit(0)
    pytest.skip(
        "Legacy device browser automation script; enable explicitly to run.",
        allow_module_level=True,
    )
import time
import requests
from typing import Optional, List

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import (
    TimeoutException,
    NoSuchElementException,
    ElementNotInteractableException,
)

_DEFAULT_DEVICE_HOST = "127.0.0.1:5000"


def _device_base_url() -> str:
    raw = os.environ.get("LED_CONTROLLER_DEVICE_IP", _DEFAULT_DEVICE_HOST).strip()
    if not raw:
        raw = _DEFAULT_DEVICE_HOST
    if raw.startswith(("http://", "https://")):
        return raw.rstrip("/")
    return f"http://{raw}"


# Base URL for the device
BASE_URL = _device_base_url()


def _browser_sleep(seconds: float) -> None:
    """Scale fixed UI pauses via LED_CONTROLLER_BROWSER_SLEEP_SCALE (default 0.5)."""
    try:
        scale = float(os.environ.get("LED_CONTROLLER_BROWSER_SLEEP_SCALE", "0.5"))
    except ValueError:
        scale = 0.5
    if scale <= 0:
        return
    time.sleep(max(0.0, float(seconds)) * scale)


def _implicit_wait_s() -> int:
    try:
        v = float(os.environ.get("LED_CONTROLLER_BROWSER_IMPLICIT_WAIT", "2"))
    except ValueError:
        v = 2.0
    return int(max(0, min(60, round(v))))


class BrowserTest:
    """Browser automation test class."""
    
    def __init__(self, base_url: str = BASE_URL, headless: bool = False):
        self.base_url = base_url
        self.driver = None
        self.headless = headless
        self.created_zones: List[str] = []
        self.created_profiles: List[str] = []
        self.created_presets: List[str] = []
    
    def setup(self):
        """Set up the browser driver. Tries Chrome first, then Firefox."""
        err_chrome, err_firefox = None, None
        # Try Chrome first
        try:
            opts = ChromeOptions()
            if self.headless:
                opts.add_argument('--headless')
            opts.add_argument('--no-sandbox')
            opts.add_argument('--disable-dev-shm-usage')
            opts.add_argument('--disable-gpu')
            opts.add_argument('--window-size=1920,1080')
            self.driver = webdriver.Chrome(options=opts)
            self.driver.implicitly_wait(_implicit_wait_s())
            print("✓ Browser started (Chrome)")
            return True
        except Exception as e:
            err_chrome = e
        # Fallback to Firefox
        try:
            opts = FirefoxOptions()
            if self.headless:
                opts.add_argument('--headless')
            self.driver = webdriver.Firefox(options=opts)
            self.driver.implicitly_wait(_implicit_wait_s())
            print("✓ Browser started (Firefox)")
            return True
        except Exception as e:
            err_firefox = e
        print("✗ Failed to start browser.")
        if err_chrome:
            print(f"  Chrome: {err_chrome}")
        if err_firefox:
            print(f"  Firefox: {err_firefox}")
        print("  On Raspberry Pi (aarch64), install: chromium-browser and chromium-chromedriver")
        return False
    
    def teardown(self):
        """Close the browser."""
        if self.driver:
            self.driver.quit()
            print("✓ Browser closed")
    
    def navigate(self, path: str = '/'):
        """Navigate to a URL."""
        url = f"{self.base_url}{path}"
        try:
            self.driver.get(url)
            _browser_sleep(1)  # Wait for page load
            return True
        except Exception as e:
            print(f"✗ Failed to navigate to {url}: {e}")
            return False
    
    def wait_for_element(self, by, value, timeout=10):
        """Wait for an element to appear."""
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.presence_of_element_located((by, value))
            )
            return element
        except TimeoutException:
            return None

    def _scroll_into_view(self, element) -> None:
        self.driver.execute_script(
            "arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});",
            element,
        )
    
    def click_element(self, by, value, timeout=10, use_js=False):
        """Click an element."""
        try:
            element = self.wait_for_element(by, value, timeout)
            if element:
                self._scroll_into_view(element)
                if use_js:
                    # Use JavaScript click for elements that might be intercepted
                    self.driver.execute_script("arguments[0].click();", element)
                else:
                    # Try normal click first, fall back to JS if it fails
                    try:
                        element.click()
                    except Exception:
                        self.driver.execute_script("arguments[0].click();", element)
                _browser_sleep(0.5)  # Wait for action
                return True
            return False
        except Exception as e:
            print(f"✗ Failed to click {value}: {e}")
            return False
    
    def handle_alert(self, accept=True, timeout=2):
        """Handle an alert dialog."""
        try:
            alert = WebDriverWait(self.driver, timeout).until(EC.alert_is_present())
            if accept:
                alert.accept()
            else:
                alert.dismiss()
            _browser_sleep(0.3)
            return True
        except TimeoutException:
            return False
        except Exception as e:
            print(f"✗ Failed to handle alert: {e}")
            return False
    
    def cleanup_test_data(self):
        """Clean up test data created during tests."""
        print("\n  Cleaning up test data...")
        try:
            # Use requests to make API calls for cleanup
            session = requests.Session()
            
            # Delete created presets by ID
            for preset_id in self.created_presets:
                try:
                    response = session.delete(f"{self.base_url}/presets/{preset_id}")
                    if response.status_code == 200:
                        print(f"    ✓ Cleaned up preset: {preset_id}")
                except Exception as e:
                    print(f"    ⚠ Failed to cleanup preset {preset_id}: {e}")
            
            # Delete created zones by ID
            for zone_id in self.created_zones:
                try:
                    response = session.delete(f"{self.base_url}/zones/{zone_id}")
                    if response.status_code == 200:
                        print(f"    ✓ Cleaned up zone: {zone_id}")
                except Exception as e:
                    print(f"    ⚠ Failed to cleanup zone {zone_id}: {e}")
            
            # Delete created profiles by ID
            for profile_id in self.created_profiles:
                try:
                    response = session.delete(f"{self.base_url}/profiles/{profile_id}")
                    if response.status_code == 200:
                        print(f"    ✓ Cleaned up profile: {profile_id}")
                except Exception as e:
                    print(f"    ⚠ Failed to cleanup profile {profile_id}: {e}")
            
            # Also try to cleanup by name pattern (in case IDs weren't tracked)
            test_names = ['Browser Test Zone', 'Browser Test Profile', 'Browser Test Preset', 
                         'Preset 1', 'Preset 2', 'Preset 3', 'Edited Browser Zone']
            
            # Cleanup zones by name
            try:
                zones_response = session.get(f"{self.base_url}/zones")
                if zones_response.status_code == 200:
                    zones_data = zones_response.json()
                    zones_map = zones_data.get('zones', {})
                    for zone_id, zone_row in zones_map.items():
                        if isinstance(zone_row, dict) and zone_row.get('name') in test_names:
                            try:
                                session.delete(f"{self.base_url}/zones/{zone_id}")
                                print(f"    ✓ Cleaned up zone by name: {zone_row.get('name')}")
                            except:
                                pass
            except:
                pass
            
            # Cleanup profiles by name
            try:
                profiles_response = session.get(f"{self.base_url}/profiles")
                if profiles_response.status_code == 200:
                    profiles_data = profiles_response.json()
                    profiles = profiles_data.get('profiles', {})
                    for profile_id, profile_data in profiles.items():
                        if isinstance(profile_data, dict) and profile_data.get('name') in test_names:
                            try:
                                session.delete(f"{self.base_url}/profiles/{profile_id}")
                                print(f"    ✓ Cleaned up profile by name: {profile_data.get('name')}")
                            except:
                                pass
            except:
                pass
            
            # Cleanup presets by name
            try:
                presets_response = session.get(f"{self.base_url}/presets")
                if presets_response.status_code == 200:
                    presets_data = presets_response.json()
                    presets = presets_data.get('presets', {}) if isinstance(presets_data, dict) else presets_data
                    for preset_id, preset_data in presets.items():
                        if isinstance(preset_data, dict) and preset_data.get('name') in test_names:
                            try:
                                session.delete(f"{self.base_url}/presets/{preset_id}")
                                print(f"    ✓ Cleaned up preset by name: {preset_data.get('name')}")
                            except:
                                pass
            except:
                pass
            
            # Clear the lists
            self.created_zones.clear()
            self.created_profiles.clear()
            self.created_presets.clear()
        except Exception as e:
            print(f"    ⚠ Cleanup error: {e}")
    
    def fill_input(self, by, value, text, timeout=10):
        """Fill an input field."""
        try:
            element = self.wait_for_element(by, value, timeout)
            if element:
                self._scroll_into_view(element)
                # Chrome often reports <input type="color"> as not interactable for clear/send_keys.
                if (element.get_attribute("type") or "").lower() == "color":
                    hex_v = text.strip()
                    if hex_v and not hex_v.startswith("#"):
                        hex_v = "#" + hex_v
                    self.driver.execute_script(
                        """
                        var el = arguments[0], v = arguments[1];
                        el.value = v;
                        el.dispatchEvent(new Event('input', {bubbles: true}));
                        el.dispatchEvent(new Event('change', {bubbles: true}));
                        """,
                        element,
                        hex_v,
                    )
                    return True
                element.clear()
                try:
                    element.send_keys(text)
                except ElementNotInteractableException:
                    self.driver.execute_script(
                        "arguments[0].value = arguments[1];"
                        "arguments[0].dispatchEvent(new Event('input', {bubbles: true}));"
                        "arguments[0].dispatchEvent(new Event('change', {bubbles: true}));",
                        element,
                        text,
                    )
                return True
            return False
        except Exception as e:
            print(f"✗ Failed to fill {value}: {e}")
            return False
    
    def get_text(self, by, value, timeout=10):
        """Get text from an element."""
        try:
            element = self.wait_for_element(by, value, timeout)
            if element:
                return element.text
            return None
        except Exception as e:
            return None
    
    def get_cookie(self, name: str):
        """Get a cookie value."""
        try:
            return self.driver.get_cookie(name)
        except:
            return None
    
    def drag_and_drop(self, source_element, target_element):
        """Perform drag and drop operation."""
        try:
            actions = ActionChains(self.driver)
            actions.drag_and_drop(source_element, target_element).perform()
            _browser_sleep(0.5)  # Wait for drop to complete
            return True
        except Exception as e:
            print(f"✗ Drag and drop failed: {e}")
            return False
    
    def drag_and_drop_by_offset(self, element, x_offset, y_offset):
        """Perform drag and drop by offset."""
        try:
            actions = ActionChains(self.driver)
            actions.drag_and_drop_by_offset(element, x_offset, y_offset).perform()
            _browser_sleep(0.5)
            return True
        except Exception as e:
            print(f"✗ Drag and drop by offset failed: {e}")
            return False


@pytest.fixture
def browser() -> BrowserTest:
    return BrowserTest()


def test_browser_connection(browser: BrowserTest) -> bool:
    """Test basic browser connection."""
    print("Testing browser connection...")
    if not browser.setup():
        return False
    
    try:
        if browser.navigate('/'):
            print("✓ Successfully loaded page")
            title = browser.driver.title
            print(f"  Page title: {title}")
            return True
        else:
            print("✗ Failed to load page")
            return False
    finally:
        browser.teardown()

def test_zones_ui(browser: BrowserTest) -> bool:
    """Test zones UI in browser."""
    print("\n=== Testing Zones UI in Browser ===")
    passed = 0
    total = 0
    
    if not browser.setup():
        return False
    
    try:
        # Test 1: Load page
        total += 1
        if browser.navigate('/'):
            print("✓ Loaded main page")
            passed += 1
        else:
            print("✗ Failed to load main page")
            browser.teardown()
            return False
        
        # Test 2: Open zones modal
        total += 1
        if browser.click_element(By.ID, 'zones-btn'):
            print("✓ Clicked Zones button")
            # Wait for modal to appear
            _browser_sleep(0.5)
            modal = browser.wait_for_element(By.ID, 'zones-modal')
            if modal and 'active' in modal.get_attribute('class'):
                print("✓ Zones modal opened")
                passed += 1
            else:
                print("✗ Zones modal didn't open")
        else:
            print("✗ Failed to click Zones button")
        
        # Test 3: Create a zone via UI
        total += 1
        try:
            # Fill in zone name
            if browser.fill_input(By.ID, 'new-zone-name', 'Browser Test Zone'):
                print("  ✓ Filled zone name")
            # Devices default from registry or placeholder name "1"
            # Click create button
            if browser.click_element(By.ID, 'create-zone-btn'):
                print("  ✓ Clicked create button")
                _browser_sleep(1)  # Wait for creation
                # Check if zone appears in list and extract ID
                tabs_list = browser.wait_for_element(By.ID, 'zones-list-modal')
                if tabs_list:
                    list_text = tabs_list.text
                    if 'Browser Test Zone' in list_text:
                        print("✓ Created zone via UI")
                        # Try to extract zone ID from the list (look for data-zone-id attribute)
                        try:
                            tab_rows = browser.driver.find_elements(By.CSS_SELECTOR, '#zones-list-modal .profiles-row')
                            for row in tab_rows:
                                if 'Browser Test Zone' in row.text:
                                    zone_id = row.get_attribute('data-zone-id')
                                    if zone_id:
                                        browser.created_zones.append(zone_id)
                                    break
                        except:
                            pass  # If we can't extract ID, cleanup will try by name
                        passed += 1
                    else:
                        print("✗ Zone not found in list after creation")
                else:
                    print("✗ Zones list not found")
            else:
                print("✗ Failed to click create button")
        except Exception as e:
            print(f"✗ Failed to create zone via UI: {e}")
        
        # Test 4: Edit a zone via UI (right-click in zones list)
        total += 1
        try:
            # First, close and reopen modal to refresh
            browser.click_element(By.ID, 'zones-close-btn')
            _browser_sleep(0.5)
            browser.click_element(By.ID, 'zones-btn')
            _browser_sleep(0.5)
            
            # Right-click the row corresponding to 'Browser Test Zone'
            try:
                tab_row = browser.driver.find_element(
                    By.XPATH,
                    "//div[@id='zones-list-modal']//div[contains(@class,'profiles-row')][.//span[contains(text(), 'Browser Test Zone')]]"
                )
            except Exception:
                tab_row = None

            if tab_row:
                actions = ActionChains(browser.driver)
                actions.context_click(tab_row).perform()
                _browser_sleep(0.5)

                # Check if edit modal opened
                edit_modal = browser.wait_for_element(By.ID, 'edit-zone-modal')
                if edit_modal:
                    print("✓ Edit modal opened via right-click")
                    # Fill in new name
                    if browser.fill_input(By.ID, 'edit-zone-name', 'Edited Browser Zone'):
                        print("  ✓ Filled new zone name")
                    # Submit form
                    edit_form = browser.wait_for_element(By.ID, 'edit-zone-form')
                    if edit_form:
                        browser.driver.execute_script("arguments[0].submit();", edit_form)
                        _browser_sleep(1)  # Wait for update
                        print("✓ Submitted edit form")
                        passed += 1
                    else:
                        print("✗ Edit form not found")
                else:
                    print("✗ Edit modal didn't open after right-click")
            else:
                print("✗ Could not find zone row for 'Browser Test Zone'")
        except Exception as e:
            print(f"✗ Failed to edit zone via UI: {e}")
            import traceback
            traceback.print_exc()
        
        # Test 5: Check current zone cookie
        total += 1
        cookie = browser.get_cookie('current_zone')
        if cookie:
            print(f"✓ Found current_zone cookie: {cookie.get('value')}")
            passed += 1
        else:
            print("⚠ No current_zone cookie found (might be normal if no zone selected)")
            passed += 1  # Not a failure, just informational
        
        # Close modal
        browser.click_element(By.ID, 'zones-close-btn')
        
    except Exception as e:
        print(f"✗ Browser test error: {e}")
        import traceback
        traceback.print_exc()
    finally:
        browser.cleanup_test_data()
        browser.teardown()
    
    print(f"\nBrowser zones UI tests: {passed}/{total} passed")
    return passed == total

def test_profiles_ui(browser: BrowserTest) -> bool:
    """Test profiles UI in browser."""
    print("\n=== Testing Profiles UI in Browser ===")
    passed = 0
    total = 0
    
    if not browser.setup():
        return False
    
    try:
        # Test 1: Load page
        total += 1
        if browser.navigate('/'):
            print("✓ Loaded main page")
            passed += 1
        else:
            browser.teardown()
            return False
        
        # Test 2: Open profiles modal
        total += 1
        if browser.click_element(By.ID, 'profiles-btn'):
            print("✓ Clicked Profiles button")
            _browser_sleep(0.5)
            modal = browser.wait_for_element(By.ID, 'profiles-modal')
            if modal:
                print("✓ Profiles modal opened")
                passed += 1
            else:
                print("✗ Profiles modal didn't open")
        
        # Test 3: Create profile via UI
        total += 1
        try:
            if browser.fill_input(By.ID, 'new-profile-name', 'Browser Test Profile'):
                print("  ✓ Filled profile name")
            if browser.click_element(By.ID, 'create-profile-btn'):
                print("  ✓ Clicked create button")
                _browser_sleep(1)
                # Check if profile appears
                profiles_list = browser.wait_for_element(By.ID, 'profiles-list')
                if profiles_list and 'Browser Test Profile' in profiles_list.text:
                    print("✓ Created profile via UI")
                    passed += 1
                else:
                    print("✗ Profile not found in list")
        except Exception as e:
            print(f"✗ Failed to create profile: {e}")
        
        # Close modal
        browser.click_element(By.ID, 'profiles-close-btn')
        
    except Exception as e:
        print(f"✗ Browser test error: {e}")
        import traceback
        traceback.print_exc()
    finally:
        browser.cleanup_test_data()
        browser.teardown()
    
    print(f"\nBrowser profiles UI tests: {passed}/{total} passed")
    return passed == total


def test_mobile_tab_presets_two_columns():
    """
    Verify that the zone preset selecting area shows roughly two preset tiles per row
    on a phone-sized viewport.
    """
    bt = BrowserTest(base_url=BASE_URL, headless=True)
    if not bt.setup():
        assert False, "Failed to start browser"

    try:
        # Simulate a mobile viewport
        bt.driver.set_window_size(400, 800)
        assert bt.navigate('/'), "Failed to load main page"

        # Click the first zone button to load presets for that zone
        first_tab = bt.wait_for_element(By.CSS_SELECTOR, '.zone-button', timeout=10)
        assert first_tab is not None, "No zone buttons found"
        first_tab.click()
        _browser_sleep(1)

        container = bt.wait_for_element(By.ID, 'presets-list-zone', timeout=10)
        assert container is not None, "presets-list-zone not found"

        tiles = bt.driver.find_elements(By.CSS_SELECTOR, '#presets-list-zone .preset-tile-row')
        # Need at least 2 presets to make this meaningful
        assert len(tiles) >= 2, "Fewer than 2 presets found for zone"

        container_width = container.size['width']
        first_width = tiles[0].size['width']

        # Each tile should be about half the container width (tolerate some margin)
        assert 0.4 * container_width <= first_width <= 0.6 * container_width, (
            f"Preset tile width {first_width} not ~half of container {container_width}"
        )
    finally:
        bt.teardown()

def test_presets_ui(browser: BrowserTest) -> bool:
    """Test presets UI in browser."""
    print("\n=== Testing Presets UI in Browser ===")
    passed = 0
    total = 0
    
    if not browser.setup():
        return False
    
    try:
        # Test 1: Load page
        total += 1
        if browser.navigate('/'):
            print("✓ Loaded main page")
            passed += 1
        else:
            browser.teardown()
            return False
        
        # Test 2: Open presets modal
        total += 1
        if browser.click_element(By.ID, 'presets-btn'):
            print("✓ Clicked Presets button")
            _browser_sleep(0.5)
            modal = browser.wait_for_element(By.ID, 'presets-modal')
            if modal:
                print("✓ Presets modal opened")
                passed += 1
            else:
                print("✗ Presets modal didn't open")
        
        # Test 3: Click Add button to open preset editor
        total += 1
        try:
            if browser.click_element(By.ID, 'preset-add-btn'):
                print("  ✓ Clicked Add Preset button")
                _browser_sleep(0.5)
                editor_modal = browser.wait_for_element(By.ID, 'preset-editor-modal')
                if editor_modal:
                    print("✓ Preset editor modal opened")
                    passed += 1
                else:
                    print("✗ Preset editor modal didn't open")
            else:
                print("✗ Failed to click Add Preset button")
        except Exception as e:
            print(f"✗ Failed to open preset editor: {e}")
        
        # Test 4: Create a preset via UI
        total += 1
        try:
            # Fill preset name
            if browser.fill_input(By.ID, 'preset-name-input', 'Browser Test Preset'):
                print("  ✓ Filled preset name")
            # Select a pattern
            pattern_select = browser.wait_for_element(By.ID, 'preset-pattern-input')
            if pattern_select:
                # Try to select first available pattern
                from selenium.webdriver.support.ui import Select
                select = Select(pattern_select)
                if len(select.options) > 1:  # More than just the placeholder
                    select.select_by_index(1)  # Select first real pattern
                    print("  ✓ Selected pattern")
            # Add a color
            if browser.fill_input(By.ID, 'preset-new-color', '#ff0000'):
                print("  ✓ Set color")
            if browser.click_element(By.ID, 'preset-add-color-btn'):
                print("  ✓ Added color")
            # Set brightness
            if browser.fill_input(By.ID, 'preset-brightness-input', '200'):
                print("  ✓ Set brightness")
            # Save preset
            if browser.click_element(By.ID, 'preset-save-btn'):
                print("  ✓ Clicked save button")
                _browser_sleep(1)
                # Check if preset appears in list
                presets_list = browser.wait_for_element(By.ID, 'presets-list')
                if presets_list and 'Browser Test Preset' in presets_list.text:
                    print("✓ Created preset via UI")
                    passed += 1
                else:
                    print("✗ Preset not found in list after creation")
            else:
                print("✗ Failed to click save button")
        except Exception as e:
            print(f"✗ Failed to create preset: {e}")
            import traceback
            traceback.print_exc()
        
        # Close editor modal
        browser.click_element(By.ID, 'preset-editor-close-btn', use_js=True)
        _browser_sleep(0.5)
        # Close presets modal
        browser.click_element(By.ID, 'presets-close-btn', use_js=True)
        
    except Exception as e:
        print(f"✗ Browser test error: {e}")
        import traceback
        traceback.print_exc()
    finally:
        browser.cleanup_test_data()
        browser.teardown()
    
    print(f"\nBrowser presets UI tests: {passed}/{total} passed")
    return passed == total

def test_color_palette_ui(browser: BrowserTest) -> bool:
    """Test color palette UI in browser."""
    print("\n=== Testing Color Palette UI in Browser ===")
    passed = 0
    total = 0
    
    if not browser.setup():
        return False
    
    try:
        # Test 1: Load page
        total += 1
        if browser.navigate('/'):
            print("✓ Loaded main page")
            passed += 1
        else:
            browser.teardown()
            return False
        
        # Test 2: Open color palette modal
        total += 1
        if browser.click_element(By.ID, 'color-palette-btn'):
            print("✓ Clicked Color Palette button")
            _browser_sleep(0.5)
            modal = browser.wait_for_element(By.ID, 'color-palette-modal')
            if modal:
                print("✓ Color palette modal opened")
                passed += 1
            else:
                print("✗ Color palette modal didn't open")
        
        # Test 3: Add a color to palette
        total += 1
        try:
            # Set color picker - use a unique color that's likely not in palette
            color_input = browser.wait_for_element(By.ID, 'palette-new-color')
            if color_input:
                # Use a unique color (purple) that's less likely to be in palette
                browser.driver.execute_script("arguments[0].value = '#9b59b6'; arguments[0].dispatchEvent(new Event('input'));", color_input)
                print("  ✓ Set color to #9b59b6")
            # Click add color button
            if browser.click_element(By.ID, 'palette-add-color-btn'):
                print("  ✓ Clicked Add Color button")
                _browser_sleep(0.5)
                # Handle alert if color already exists
                browser.handle_alert(accept=True, timeout=1)
                # Check if color appears in palette
                palette_container = browser.wait_for_element(By.ID, 'palette-container')
                if palette_container:
                    # Look for color swatches
                    color_swatches = browser.driver.find_elements(By.CSS_SELECTOR, '#palette-container .draggable-color-swatch, #palette-container [style*="background-color"]')
                    if color_swatches:
                        print("✓ Added color to palette")
                        passed += 1
                    else:
                        print("✗ Color not found in palette after adding")
                else:
                    print("✗ Palette container not found")
            else:
                print("✗ Failed to click Add Color button")
        except Exception as e:
            print(f"✗ Failed to add color: {e}")
            import traceback
            traceback.print_exc()
        
        # Test 4: Test drag and drop colors (if multiple colors exist)
        total += 1
        try:
            color_swatches = browser.driver.find_elements(By.CSS_SELECTOR, '#palette-container .draggable-color-swatch, #palette-container [draggable="true"]')
            if len(color_swatches) >= 2:
                # Drag first color to second position
                source = color_swatches[0]
                target = color_swatches[1]
                if browser.drag_and_drop(source, target):
                    print("✓ Dragged color to reorder")
                    _browser_sleep(0.5)
                    passed += 1
                else:
                    print("✗ Drag and drop failed")
            else:
                print("⚠ Not enough colors to test drag and drop (need at least 2)")
                passed += 1  # Not a failure
        except Exception as e:
            print(f"✗ Drag and drop test error: {e}")
        
        # Close modal
        browser.click_element(By.ID, 'color-palette-close-btn', use_js=True)
        
    except Exception as e:
        print(f"✗ Browser test error: {e}")
        import traceback
        traceback.print_exc()
    finally:
        browser.cleanup_test_data()
        browser.teardown()
    
    print(f"\nBrowser color palette UI tests: {passed}/{total} passed")
    return passed >= total - 1  # Allow one failure (alert handling might be flaky)

def test_preset_drag_and_drop(browser: BrowserTest) -> bool:
    """Test dragging presets around in a zone."""
    print("\n=== Testing Preset Drag and Drop in Zone ===")
    passed = 0
    total = 0
    
    if not browser.setup():
        return False
    
    try:
        # Test 1: Load page and ensure we have a zone
        total += 1
        if browser.navigate('/'):
            print("✓ Loaded main page")
            passed += 1
        else:
            browser.teardown()
            return False
        
        # Test 2: Open zones modal and create/select a zone
        total += 1
        browser.click_element(By.ID, 'zones-btn')
        _browser_sleep(0.5)
        
        # Check if we have zones, if not create one
        tabs_list = browser.wait_for_element(By.ID, 'zones-list-modal')
        if tabs_list and 'No zones found' in tabs_list.text:
            # Create a zone
            browser.fill_input(By.ID, 'new-zone-name', 'Drag Test Zone')
            browser.click_element(By.ID, 'create-zone-btn')
            _browser_sleep(1)
        
        # Select first zone (or the one we just created)
        select_buttons = browser.driver.find_elements(By.XPATH, "//button[contains(text(), 'Select')]")
        if select_buttons:
            select_buttons[0].click()
            _browser_sleep(1)
            print("✓ Selected a zone")
            passed += 1
        else:
            print("✗ No zones available to select")
            browser.click_element(By.ID, 'zones-close-btn')
            browser.teardown()
            return False
        
        browser.click_element(By.ID, 'zones-close-btn', use_js=True)
        _browser_sleep(0.5)
        
        # Test 3: Open presets modal and create presets
        total += 1
        browser.click_element(By.ID, 'presets-btn')
        _browser_sleep(0.5)
        
        # Create first preset
        browser.click_element(By.ID, 'preset-add-btn')
        _browser_sleep(0.5)
        browser.fill_input(By.ID, 'preset-name-input', 'Preset 1')
        browser.fill_input(By.ID, 'preset-new-color', '#ff0000')
        browser.click_element(By.ID, 'preset-add-color-btn')
        browser.click_element(By.ID, 'preset-save-btn')
        _browser_sleep(1)
        
        # Create second preset
        browser.click_element(By.ID, 'preset-add-btn')
        _browser_sleep(0.5)
        browser.fill_input(By.ID, 'preset-name-input', 'Preset 2')
        browser.fill_input(By.ID, 'preset-new-color', '#00ff00')
        browser.click_element(By.ID, 'preset-add-color-btn')
        browser.click_element(By.ID, 'preset-save-btn')
        _browser_sleep(1)
        
        # Create third preset
        browser.click_element(By.ID, 'preset-add-btn')
        _browser_sleep(0.5)
        browser.fill_input(By.ID, 'preset-name-input', 'Preset 3')
        browser.fill_input(By.ID, 'preset-new-color', '#0000ff')
        browser.click_element(By.ID, 'preset-add-color-btn')
        browser.click_element(By.ID, 'preset-save-btn')
        _browser_sleep(1)
        
        browser.click_element(By.ID, 'presets-close-btn', use_js=True)
        _browser_sleep(0.5)
        
        print("✓ Created 3 presets for drag test")
        passed += 1
        
        # Test 4: Add presets to the zone (via Edit Zone modal – Add buttons in list)
        total += 1
        try:
            zone_id = browser.driver.execute_script(
                "return (window.tabsManager && window.tabsManager.getCurrentTabId && window.tabsManager.getCurrentTabId()) || null;"
            )
            if not zone_id:
                print("✗ Could not get current zone id")
            else:
                browser.driver.execute_script(
                    "if (window.tabsManager && window.tabsManager.openEditTabModal) { window.tabsManager.openEditTabModal(arguments[0], null); }",
                    zone_id
                )
                _browser_sleep(1)
                list_el = browser.wait_for_element(By.ID, 'edit-zone-presets-list', timeout=5)
                if list_el:
                    select_buttons = browser.driver.find_elements(By.XPATH, "//div[@id='edit-zone-presets-list']//button[text()='Add']")
                    if len(select_buttons) >= 2:
                        browser.driver.execute_script("arguments[0].click();", select_buttons[0])
                        _browser_sleep(1.5)
                        browser.handle_alert(accept=True, timeout=1)
                        select_buttons = browser.driver.find_elements(By.XPATH, "//div[@id='edit-zone-presets-list']//button[text()='Add']")
                        if len(select_buttons) >= 1:
                            browser.driver.execute_script("arguments[0].click();", select_buttons[0])
                            _browser_sleep(1.5)
                            browser.handle_alert(accept=True, timeout=1)
                            print("  ✓ Added 2 presets to zone")
                        passed += 1
                    elif len(select_buttons) == 1:
                        browser.driver.execute_script("arguments[0].click();", select_buttons[0])
                        _browser_sleep(1.5)
                        browser.handle_alert(accept=True, timeout=1)
                        print("  ✓ Added 1 preset to zone")
                        passed += 1
                    else:
                        print("  ⚠ No presets available to add (all already in zone)")
                else:
                    print("✗ Edit zone presets list not found")
        except Exception as e:
            print(f"✗ Failed to add presets to zone: {e}")
            import traceback
            traceback.print_exc()
        
        # Test 5: Find presets in zone and test drag and drop (Edit mode only)
        total += 1
        try:
            # Wait for presets to load in the zone
            presets_list_tab = browser.wait_for_element(By.ID, 'presets-list-zone', timeout=5)
            if presets_list_tab:
                _browser_sleep(1)  # Wait for presets to render

                # Reordering is only available in Edit mode (tiles get .draggable-preset)
                mode_toggle = browser.wait_for_element(By.CSS_SELECTOR, '.ui-mode-toggle', timeout=5)
                if mode_toggle and mode_toggle.get_attribute('aria-pressed') == 'false':
                    mode_toggle.click()
                    _browser_sleep(0.5)

                # Find draggable preset elements - wait a bit more for rendering
                _browser_sleep(1)
                draggable_presets = browser.driver.find_elements(By.CSS_SELECTOR, '#presets-list-zone .draggable-preset')
                if len(draggable_presets) >= 2:
                    print(f"  ✓ Found {len(draggable_presets)} draggable presets")
                    
                    # Get initial order
                    initial_order = [p.text for p in draggable_presets]
                    print(f"  Initial order: {initial_order[:3]}")  # Show first 3
                    
                    # Drag first preset down
                    source = draggable_presets[0]
                    target = draggable_presets[1]
                    
                    # Use ActionChains for drag and drop
                    actions = ActionChains(browser.driver)
                    actions.click_and_hold(source).move_to_element(target).release().perform()
                    _browser_sleep(1)  # Wait for reorder to complete
                    
                    # Check if order changed
                    draggable_presets_after = browser.driver.find_elements(By.CSS_SELECTOR, '#presets-list-zone .draggable-preset')
                    if len(draggable_presets_after) >= 2:
                        new_order = [p.text for p in draggable_presets_after]
                        print(f"  New order: {new_order[:3]}")
                        
                        if initial_order != new_order:
                            print("✓ Preset drag and drop worked - order changed")
                            passed += 1
                        else:
                            print("⚠ Order didn't change (might need to verify with API)")
                            passed += 1  # Not necessarily a failure
                    else:
                        print("✗ Presets disappeared after drag")
                elif len(draggable_presets) == 1:
                    print(f"⚠ Only 1 preset found in zone (need 2 for drag test). Preset: {draggable_presets[0].text}")
                    zone_id = browser.driver.execute_script(
                        "return (window.tabsManager && window.tabsManager.getCurrentTabId && window.tabsManager.getCurrentTabId()) || null;"
                    )
                    if zone_id:
                        browser.driver.execute_script(
                            "if (window.tabsManager && window.tabsManager.openEditTabModal) { window.tabsManager.openEditTabModal(arguments[0], null); }",
                            zone_id
                        )
                        _browser_sleep(1)
                    select_buttons = browser.driver.find_elements(By.XPATH, "//div[@id='edit-zone-presets-list']//button[text()='Add']")
                    if select_buttons:
                        print("  Attempting to add another preset...")
                        browser.driver.execute_script("arguments[0].click();", select_buttons[0])
                        _browser_sleep(1.5)
                        browser.handle_alert(accept=True, timeout=1)
                        try:
                            browser.driver.execute_script("document.getElementById('edit-zone-modal').classList.remove('active');")
                        except Exception:
                            pass
                        _browser_sleep(1)
                        draggable_presets = browser.driver.find_elements(By.CSS_SELECTOR, '#presets-list-zone .draggable-preset')
                        if len(draggable_presets) >= 2:
                            print("  ✓ Added another preset, now testing drag...")
                            source = draggable_presets[0]
                            target = draggable_presets[1]
                            actions = ActionChains(browser.driver)
                            actions.click_and_hold(source).move_to_element(target).release().perform()
                            _browser_sleep(1)
                            print("✓ Performed drag and drop")
                            passed += 1
                        else:
                            print(f"  ✗ Still only {len(draggable_presets)} preset(s) after adding")
                    else:
                        print("  ✗ No Add buttons found in Edit Zone modal")
                else:
                    print(f"✗ No presets found in zone (found {len(draggable_presets)})")
            else:
                print("✗ Presets list in zone not found")
        except Exception as e:
            print(f"✗ Drag and drop test error: {e}")
            import traceback
            traceback.print_exc()
        
    except Exception as e:
        print(f"✗ Browser test error: {e}")
        import traceback
        traceback.print_exc()
    finally:
        browser.cleanup_test_data()
        browser.teardown()

    print(f"\nBrowser preset drag and drop tests: {passed}/{total} passed")
    return passed >= total - 2  # Allow up to 2 failures (drag test and adding presets can be flaky)

def main():
    """Run all browser tests."""
    print("=" * 60)
    print("LED Controller Browser Tests")
    print(f"Testing against: {BASE_URL}")
    print("=" * 60)

    # On Pi OS Lite there is no browser by default; skip with exit 0 instead of failing
    browser = BrowserTest(headless=True)
    if not browser.setup():
        print("\nSkipped (Pi OS Lite / no browser). Install chromium-browser and")
        print("chromium-chromedriver to run browser tests, or run on Pi OS with desktop.")
        sys.exit(0)
    browser.teardown()

    browser = BrowserTest(headless=False)  # Set to True for headless mode

    results = []

    # Run browser tests
    results.append(("Browser Connection", test_browser_connection(browser)))
    results.append(("Zones UI", test_zones_ui(browser)))
    results.append(("Profiles UI", test_profiles_ui(browser)))
    results.append(("Presets UI", test_presets_ui(browser)))
    results.append(("Color Palette UI", test_color_palette_ui(browser)))
    results.append(("Preset Drag and Drop", test_preset_drag_and_drop(browser)))
    
    # Summary
    print("\n" + "=" * 60)
    print("Browser Test Summary")
    print("=" * 60)
    all_passed = True
    for name, passed in results:
        status = "✓ PASS" if passed else "✗ FAIL"
        print(f"{status} - {name}")
        if not passed:
            all_passed = False
    
    print("=" * 60)
    if all_passed:
        print("✓ All browser tests passed!")
        sys.exit(0)
    else:
        print("✗ Some browser tests failed")
        sys.exit(1)

if __name__ == "__main__":
    main()
