"""
Base test case for Lariv UI testing with Selenium and Django.

Provides a foundation for writing browser-based tests that work with
the HTMX frontend and Django's StaticLiveServerTestCase.
"""

from typing import Optional
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.contrib.auth import get_user_model
from webdriver_manager.chrome import ChromeDriverManager

User = get_user_model()


class LarivUITestCase(StaticLiveServerTestCase):
    """
    Base test case for UI testing with Selenium.

    Provides:
    - Automatic WebDriver setup/teardown with headless Chrome
    - Login helpers for authenticated tests
    - HTMX-aware wait utilities
    - Common element interaction methods

    Example:
        class StudentUITest(LarivUITestCase):
            def test_student_list(self):
                self.login()
                self.navigate_to('/app/students/list/')
                self.wait_for_htmx()
                self.assert_element_visible('.student-table')
    """

    driver: WebDriver
    wait: WebDriverWait

    # Default test user credentials
    test_email: str = "uitest@example.com"
    test_password: str = "testpassword123"
    test_name: str = "UI Test User"
    test_phone: str = "0000000000"

    # WebDriver configuration
    headless: bool = True
    window_width: int = 1920
    window_height: int = 1080
    default_timeout: int = 30  # seconds
    htmx_settle_time: float = 0.3  # seconds to wait after HTMX settles

    @classmethod
    def setUpClass(cls):
        """Set up Chrome WebDriver for the test class."""
        super().setUpClass()

        options = webdriver.ChromeOptions()
        if cls.headless:
            options.add_argument("--headless")
        options.add_argument("--disable-gpu")

        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument("--disable-extensions")
        options.add_argument("--disable-infobars")

        # Enable browser logging
        options.set_capability("goog:loggingPrefs", {"browser": "ALL"})

        cls.driver = webdriver.Chrome(
            service=Service(ChromeDriverManager().install()), options=options
        )
        cls.driver.set_window_size(cls.window_width, cls.window_height)
        cls.wait = WebDriverWait(cls.driver, cls.default_timeout)

    @classmethod
    def tearDownClass(cls):
        """Clean up WebDriver after all tests in class."""
        if hasattr(cls, "driver"):
            cls.driver.quit()
        super().tearDownClass()

    def setUp(self):
        """Set up test user and any per-test state."""
        super().setUp()
        self.user = self._create_test_user()

    def _create_test_user(self, is_superuser: bool = True) -> User:
        """Create a test user for authentication."""
        if is_superuser:
            return User.objects.create_superuser(
                email=self.test_email,
                password=self.test_password,
                name=self.test_name,
                phone=self.test_phone,
            )
        return User.objects.create_user(
            email=self.test_email,
            password=self.test_password,
            name=self.test_name,
            phone=self.test_phone,
        )

    # --- Navigation ---

    def navigate_to(self, path: str) -> None:
        """Navigate to a path relative to the live server."""
        url = f"{self.live_server_url}{path}"
        self.driver.get(url)
        self.assert_no_server_error()

    def get_current_path(self) -> str:
        """Get the current URL path (without domain)."""
        return self.driver.current_url.replace(self.live_server_url, "")

    # --- Authentication ---

    def login(
        self, email: Optional[str] = None, password: Optional[str] = None
    ) -> None:
        """
        Log in to the application.

        Navigates to the login page and authenticates with the given credentials.
        If no credentials provided, uses the test user credentials.
        """
        email = email or self.test_email
        password = password or self.test_password

        self.navigate_to("/")
        self.wait_for_url_contains("/login/")

        self.find_element(By.NAME, "username").send_keys(email)
        self.find_element(By.NAME, "password").send_keys(password)
        self.find_element(By.CSS_SELECTOR, "input[type='submit']").click()

        # Wait for redirect after login
        self.wait_for_url_not_contains("/login/")
        self.assert_no_server_error()

    def logout(self) -> None:
        """Log out of the application."""
        self.navigate_to("/app/users/logout/")

    # --- Element Finding ---

    def find_element(self, by: str, value: str) -> WebElement:
        """Find a single element, waiting for it to be present."""
        return self.wait.until(EC.presence_of_element_located((by, value)))

    def find_elements(self, by: str, value: str) -> list[WebElement]:
        """Find multiple elements."""
        return self.driver.find_elements(by, value)

    def find_clickable(self, by: str, value: str) -> WebElement:
        """Find an element and wait for it to be clickable."""
        return self.wait.until(EC.element_to_be_clickable((by, value)))

    def find_visible(self, by: str, value: str) -> WebElement:
        """Find an element and wait for it to be visible."""
        return self.wait.until(EC.visibility_of_element_located((by, value)))

    # --- HTMX-Specific Helpers ---

    def wait_for_htmx(self, timeout: Optional[int] = None) -> None:
        """
        Wait for HTMX to finish all pending requests.

        HTMX sets document.body with 'htmx-request' class during requests.
        This waits for that class to be removed and adds a small settle time.
        """
        timeout = timeout or self.default_timeout

        # Wait for htmx-request class to be removed from body
        self.wait.until(
            lambda d: (
                "htmx-request"
                not in (
                    d.find_element(By.TAG_NAME, "body").get_attribute("class") or ""
                )
            )
        )

        # Also wait for any htmx-settling to complete
        self.wait.until(
            lambda d: (
                "htmx-settling"
                not in (
                    d.find_element(By.TAG_NAME, "body").get_attribute("class") or ""
                )
            )
        )

        # Small additional wait for DOM to settle
        import time

        time.sleep(self.htmx_settle_time)
        self.assert_no_server_error()

    def click_htmx_link(self, by: str, value: str) -> None:
        """Click an HTMX-enabled link and wait for the request to complete."""
        element = self.find_clickable(by, value)
        element.click()
        self.wait_for_htmx()

    def click_link_by_text(self, text: str, partial: bool = True) -> None:
        """Find and click a link by its text content (including children)."""
        if partial:
            xpath = f"//a[contains(., '{text}')]"
        else:
            xpath = f"//a[normalize-space(.)='{text}']"
        self.click_htmx_link(By.XPATH, xpath)

    def submit_htmx_form(self, submit_selector: str = "input[type='submit']") -> None:
        """Submit a form that uses HTMX and wait for completion."""
        submit_btn = self.find_clickable(By.CSS_SELECTOR, submit_selector)
        submit_btn.click()
        self.wait_for_htmx()

    # --- Wait Helpers ---

    def wait_for_url(self, url: str) -> None:
        """Wait for the current URL to exactly match."""
        full_url = f"{self.live_server_url}{url}" if url.startswith("/") else url
        self.wait.until(EC.url_to_be(full_url))

    def wait_for_url_contains(self, substring: str) -> None:
        """Wait for the current URL to contain a substring."""
        self.wait.until(EC.url_contains(substring))

    def wait_for_url_not_contains(self, substring: str) -> None:
        """Wait for the current URL to NOT contain a substring."""
        self.wait.until_not(EC.url_contains(substring))

    def wait_for_url_matches(self, pattern: str) -> None:
        """Wait for the current URL to match a regex pattern."""
        self.wait.until(EC.url_matches(pattern))

    def wait_for_text(self, by: str, value: str, text: str) -> None:
        """Wait for an element to contain specific text."""
        self.wait.until(EC.text_to_be_present_in_element((by, value), text))

    # --- Form Helpers ---

    def fill_input(self, name: str, value: str) -> None:
        """Fill an input field by name."""
        element = self.find_element(By.NAME, name)
        element.clear()
        element.send_keys(value)

    def fill_form(self, data: dict[str, str]) -> None:
        """Fill multiple form fields from a dictionary."""
        for name, value in data.items():
            self.fill_input(name, value)

    def select_option(self, name: str, value: str) -> None:
        """Select an option from a dropdown by value."""
        from selenium.webdriver.support.ui import Select

        select = Select(self.find_element(By.NAME, name))
        select.select_by_value(value)

    def check_checkbox(self, name: str, check: bool = True) -> None:
        """Check or uncheck a checkbox."""
        checkbox = self.find_element(By.NAME, name)
        if checkbox.is_selected() != check:
            checkbox.click()

    # --- Assertions ---

    def assert_no_server_error(self) -> None:
        """Assert that the current page is not showing a server error."""
        source = self.driver.page_source
        if "Server Error (500)" in source or "Internal Server Error" in source:
            # Try to get more info from the page
            title = self.driver.title
            try:
                error_msg = self.driver.find_element(By.TAG_NAME, "h1").text
            except Exception:
                error_msg = "Unknown error"

            # Check console logs for more details
            try:
                logs = self.driver.get_log("browser")
                severe_logs = [
                    log["message"] for log in logs if log["level"] == "SEVERE"
                ]
                log_msg = "\n".join(severe_logs)
            except Exception:
                log_msg = "Could not retrieve browser logs"

            self.fail(
                f"Server Error detected: {title} - {error_msg}\nBrowser logs:\n{log_msg}"
            )

    def assert_element_visible(self, selector: str) -> None:
        """Assert that an element matching the CSS selector is visible."""
        element = self.find_visible(By.CSS_SELECTOR, selector)
        self.assertTrue(element.is_displayed())

    def assert_element_not_visible(self, selector: str) -> None:
        """Assert that an element is not visible or doesn't exist."""
        elements = self.find_elements(By.CSS_SELECTOR, selector)
        for el in elements:
            self.assertFalse(el.is_displayed())

    def assert_text_present(self, text: str) -> None:
        """Assert that text is present somewhere on the page."""
        self.assertIn(text, self.driver.page_source)

    def assert_text_not_present(self, text: str) -> None:
        """Assert that text is NOT present on the page."""
        self.assertNotIn(text, self.driver.page_source)

    def assert_url_contains(self, substring: str) -> None:
        """Assert that the current URL contains a substring."""
        self.assertIn(substring, self.driver.current_url)

    def assert_table_row_count(self, table_selector: str, expected_count: int) -> None:
        """Assert the number of rows in a table (excluding header)."""
        table = self.find_element(By.CSS_SELECTOR, table_selector)
        rows = table.find_elements(By.CSS_SELECTOR, "tbody tr")
        self.assertEqual(len(rows), expected_count)

    # --- Debugging ---

    def screenshot(self, name: str = "debug") -> str:
        """Take a screenshot for debugging. Returns the file path."""
        import tempfile
        import os

        path = os.path.join(tempfile.gettempdir(), f"{name}.png")
        self.driver.save_screenshot(path)
        return path

    def print_page_source(self) -> None:
        """Print the current page source for debugging."""
        print(self.driver.page_source)
