"""Базовый модуль для работы с компонентами страницы.

Содержит базовый класс для взаимодействия с элементами страницы через Playwright.
"""

from playwright.sync_api import Page, Locator, expect
from tools.logger import get_logger

logger = get_logger("BASE_COMPONENT")


class BaseComponent:
    """Базовый компонент для работы с элементами страницы.

    Предоставляет общие методы для взаимодействия с элементами:
    - получение локаторов
    - проверка видимости элементов
    - работа с прокруткой
    """

    def __init__(self, page: Page):
        """Инициализация базового компонента.

        Args:
            page: экземпляр страницы Playwright.
        """

        self.page = page

    # Действия:
    def get_locator(self, locator: str | Locator) -> Locator:
        """Получение объекта Locator из строки или существующего Locator.

        Args:
            locator: строка с CSS/XPath селектором или объект Locator.

        Returns:
            Locator: объект для работы с элементом.

        Raises:
            TypeError: если передан некорректный тип локатора.
        """

        if isinstance(locator, Locator):
            return locator
        elif isinstance(locator, str):
            return self.page.locator(locator)
        else:
            raise TypeError("locator value should be string type or Locator type")

    # Закомментированный код сохранен без изменений
    # def wait_for_all_elements(self, locator: Locator, timeout=5000):
    #     loc = self.get_locator(locator)
    #     elements = self.page.locator(loc).all()
    #
    #     for element in elements:
    #         self.page.locator(loc).wait_for(timeout=timeout)
    #
    #     return elements

    # Проверки:
    def check_absence(self, locator: str | Locator, msg: str) -> None:
        """Проверка отсутствия элемента на странице.

        Args:
            locator: локатор элемента (строка или объект Locator).
            msg: сообщение об ошибке при неудачной проверке.

        Raises:
            AssertionError: если элемент виден на странице.
        """

        loc = self.get_locator(locator)
        expect(loc).to_be_hidden(timeout=12000), msg

    def check_visibility(self, locator: str | Locator, msg: str) -> None:
        """Проверка видимости элемента на странице.

        Args:
            locator: локатор элемента (строка или объект Locator).
            msg: сообщение об ошибке при неудачной проверке.

        Raises:
            AssertionError: если элемент не виден на странице.
        """

        loc = self.get_locator(locator)
        expect(loc).to_be_visible(visible=True, timeout=12000), msg

    def is_scrollable_vertically(self, locator: str | Locator) -> bool:
        """Проверка возможности вертикальной прокрутки элемента.

        Args:
            locator: локатор элемента.

        Returns:
            bool: True если элемент можно прокрутить вертикально.
        """

        loc = self.get_locator(locator)
        return loc.evaluate("el => el.scrollHeight > el.clientHeight")

    def is_scrollable_horizontally(self, locator: str | Locator) -> bool:
        """Проверка возможности горизонтальной прокрутки элемента.

        Args:
            locator: локатор элемента.

        Returns:
            bool: True если элемент можно прокрутить горизонтально.
        """

        loc = self.get_locator(locator)
        return loc.evaluate("el => el.scrollWidth > el.clientWidth")

    # Методы прокрутки:
    def scroll_up(self, locator: str | Locator, timeout: int = 10000) -> None:
        """Прокрутка элемента до самого верха.

        Args:
            locator: локатор элемента.
            timeout: максимальное время ожидания в мс.

        Raises:
            AssertionError: если прокрутка не выполнена до верха.
        """

        loc = self.get_locator(locator)
        max_attempts = 3
        attempt = 0

        while attempt < max_attempts:
            # Получаем текущую позицию прокрутки
            previous_position = loc.evaluate("el => el.scrollTop")

            # Прокручиваем до верха
            loc.evaluate("el => el.scrollTo(0, 0)")

            # Ждем завершения прокрутки с использованием timeout
            loc.wait_for(timeout=min(2000, timeout))

            # Получаем новую позицию прокрутки
            current_position = loc.evaluate("el => el.scrollTop")

            # Проверяем, достигли ли мы верха
            if current_position <= 1:  # <= 1 для учета погрешности
                return  # Успешно прокрутили до верха

            # Проверяем, была ли вообще прокрутка
            if current_position >= previous_position:
                attempt += 1
            else:
                attempt = 0  # Сбрасываем счетчик, если была прокрутка

        # Если вышли из цикла - не смогли прокрутить до верха
        final_position = loc.evaluate("el => el.scrollTop")
        scroll_height = loc.evaluate("el => el.scrollHeight")
        client_height = loc.evaluate("el => el.clientHeight")

        raise AssertionError(
            f"Не удалось прокрутить до верха. "
            f"Текущая позиция: {final_position}, ожидалось: 0, "
            f"Высота контента: {scroll_height}, "
            f"Высота видимой области: {client_height}"
        )

    def scroll_down(self, locator: str | Locator, timeout: int = 10000) -> None:
        """Прокрутка элемента до самого конца.

        Args:
            locator: локатор элемента.
            timeout: максимальное время ожидания в мс.

        Raises:
            AssertionError: если прокрутка не выполнена до конца.
        """

        loc = self.get_locator(locator)
        max_attempts = 3
        attempt = 0

        while attempt < max_attempts:
            # Получаем текущую позицию прокрутки и размеры
            current_position = loc.evaluate("el => el.scrollTop")
            scroll_height = loc.evaluate("el => el.scrollHeight")
            client_height = loc.evaluate("el => el.clientHeight")

            # Проверяем, достигли ли мы конца ДО прокрутки
            if current_position + client_height >= scroll_height - 5:  # -5 для учета погрешности
                return  # Уже прокручено до конца

            # Прокручиваем до конца
            loc.evaluate("el => el.scrollTo(0, el.scrollHeight)")

            # Ждем загрузки контента
            loc.wait_for(timeout=min(2000, timeout))

            # Получаем новую позицию прокрутки
            new_position = loc.evaluate("el => el.scrollTop")
            new_scroll_height = loc.evaluate("el => el.scrollHeight")
            new_client_height = loc.evaluate("el => el.clientHeight")

            # Проверяем, достигли ли мы конца
            if new_position + new_client_height >= new_scroll_height - 5:  # -5 для учета погрешности
                return  # Успешно прокрутили до конца

            # Проверяем, была ли прокрутка
            if new_position <= current_position and new_scroll_height <= scroll_height:
                # Прокрутки не произошло или контент не изменился
                attempt += 1
            else:
                attempt = 0  # Сбрасываем счетчик, если была прокрутка

        # Если вышли из цикла - не смогли прокрутить до конца
        final_position = loc.evaluate("el => el.scrollTop")
        final_scroll_height = loc.evaluate("el => el.scrollHeight")
        final_client_height = loc.evaluate("el => el.clientHeight")
        max_scroll_y = final_scroll_height - final_client_height

        raise AssertionError(
            f"Не удалось прокрутить до конца. "
            f"Текущая позиция: {final_position}, ожидалось: {max_scroll_y}, "
            f"Высота контента: {final_scroll_height}, "
            f"Высота видимой области: {final_client_height}, "
            f"Сумма: {final_position + final_client_height}"
        )

    def scroll_left(self, locator: str | Locator, timeout: int = 10000) -> None:
        """Прокрутка элемента до самого левого края.

        Args:
            locator: локатор элемента.
            timeout: максимальное время ожидания в мс.

        Raises:
            AssertionError: если прокрутка не выполнена до левого края.
        """

        loc = self.get_locator(locator)
        max_attempts = 3
        attempt = 0

        while attempt < max_attempts:
            # Получаем текущую позицию прокрутки
            previous_position = loc.evaluate("el => el.scrollLeft")

            # Прокручиваем до левого края
            loc.evaluate("el => el.scrollTo(0, el.scrollTop)")

            # Ждем завершения прокрутки с использованием timeout
            loc.wait_for(timeout=min(2000, timeout))

            # Получаем новую позицию прокрутки
            current_position = loc.evaluate("el => el.scrollLeft")

            # Проверяем, достигли ли мы левого края
            if current_position <= 1:  # <= 1 для учета погрешности
                return  # Успешно прокрутили до левого края

            # Проверяем, была ли вообще прокрутка
            if current_position >= previous_position:
                attempt += 1
            else:
                attempt = 0  # Сбрасываем счетчик, если была прокрутка

        # Если вышли из цикла - не смогли прокрутить до левого края
        final_position = loc.evaluate("el => el.scrollLeft")
        scroll_width = loc.evaluate("el => el.scrollWidth")
        client_width = loc.evaluate("el => el.clientWidth")

        raise AssertionError(
            f"Не удалось прокрутить до левого края. "
            f"Текущая позиция: {final_position}, ожидалось: 0, "
            f"Ширина контента: {scroll_width}, "
            f"Ширина видимой области: {client_width}"
        )

    def scroll_right(self, locator: str | Locator, timeout: int = 10000) -> None:
        """Прокрутка элемента до самого правого края.

        Args:
            locator: локатор элемента.
            timeout: максимальное время ожидания в мс.

        Raises:
            AssertionError: если прокрутка не выполнена до правого края.
        """

        loc = self.get_locator(locator)
        max_attempts = 3
        attempt = 0

        while attempt < max_attempts:
            # Получаем текущую позицию прокрутки
            previous_position = loc.evaluate("el => el.scrollLeft")

            # Прокручиваем до правого края
            loc.evaluate("el => el.scrollTo(el.scrollWidth, el.scrollTop)")

            # Ждем завершения прокрутки с использованием timeout
            loc.wait_for(timeout=min(2000, timeout))

            # Получаем новую позицию прокрутки
            current_position = loc.evaluate("el => el.scrollLeft")
            scroll_width = loc.evaluate("el => el.scrollWidth")
            client_width = loc.evaluate("el => el.clientWidth")

            # Проверяем, достигли ли мы правого края
            max_scroll_x = scroll_width - client_width
            if current_position >= max_scroll_x - 1:  # -1 для учета погрешности
                return  # Успешно прокрутили до правого края

            # Проверяем, была ли вообще прокрутка
            if current_position <= previous_position:
                attempt += 1
            else:
                attempt = 0  # Сбрасываем счетчик, если была прокрутка

        # Если вышли из цикла - не смогли прокрутить до правого края
        final_position = loc.evaluate("el => el.scrollLeft")
        scroll_width = loc.evaluate("el => el.scrollWidth")
        client_width = loc.evaluate("el => el.clientWidth")
        max_scroll_x = scroll_width - client_width

        raise AssertionError(
            f"Не удалось прокрутить до правого края. "
            f"Текущая позиция: {final_position}, ожидалось: {max_scroll_x}, "
            f"Ширина контента: {scroll_width}, "
            f"Ширина видимой области: {client_width}"
        )
