"""Модуль страницы создания дочернего элемента.

Содержит класс для работы с формой создания дочернего элемента.
"""
import re
from playwright.sync_api import Page, expect

from elements.tooltip_button_element import TooltipButton
from components.toolbar_component import ToolbarComponent
from components_derived.selection_bar_component import SelectionBarComponent
from pages.main_page import MainPage
from pages.base_page import BasePage
from components.base_component import BaseComponent
from components.alert_component import AlertComponent
from components.navbar_component import NavigationPanelComponent
from locators.navigation_panel_locators import NavigationPanelLocators
from locators.combobox_locators import ComboboxLocators
from locators.rack_locators import RackLocators
from locators.alert_locators import AlertLocators
from tools.logger import get_logger

logger = get_logger("CREATE_RACK_ELEMENT")


# Словарь для сопоставления названий полей с локаторами
COMBOBOX_FIELDS_MAP = {
    # Обязательные поля
    "Имя": RackLocators.RACK_NAME_FIELD,
    "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
    "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD,

    # Дополнительные текстовые поля
    "Серийный номер": RackLocators.RACK_SERIAL_FIELD,
    "Инвентарный номер": RackLocators.RACK_INVENTORY_FIELD,
    "Комментарий": RackLocators.RACK_COMMENT_FIELD,

    # Combobox поля
    "Ввод кабеля": RackLocators.RACK_CABLE_ENTRY_FIELD,
    "Состояние": RackLocators.RACK_STATE_FIELD,
    "Владелец": RackLocators.RACK_OWNER_FIELD,
    "Обслуживающая организация": RackLocators.RACK_SERVICE_ORG_FIELD,
    "Проект/Титул": RackLocators.RACK_PROJECT_FIELD
}


class CreateRackElementTab(BasePage):
    """Класс для работы с формой создания дочернего элемента."""

    def __init__(self, page: Page) -> None:
        """
        Инициализирует объект формы создания дочернего элемента.

        Args:
            page: Экземпляр страницы Playwright
        """
        super().__init__(page)

        # Инициализация BaseComponent
        self.base_component = BaseComponent(page)

        # Инициализация AlertComponent
        self.alert = AlertComponent(page)

        # Инициализация MainPage для работы с навигацией
        self.main_page = MainPage(page)

        # Инициализация NavigationPanelComponent
        self.navigation_panel = NavigationPanelComponent(page)

        # Кнопка "Добавить" - первая кнопка в тулбаре
        create_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(0)

        # Кнопка "Отменить" - вторая кнопка в тулбаре
        cancel_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(1)

        # Инициализация кнопок
        self.create_button = TooltipButton(page, create_button_locator, "add")
        self.cancel_button = TooltipButton(page, cancel_button_locator, "cancel")

        # Инициализация тулбара с обеими кнопками
        self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в")
        self.toolbar.add_tooltip_button(create_button_locator, "add")
        self.toolbar.add_tooltip_button(cancel_button_locator, "cancel")

        # Инициализация компонента панели выбора значения для работы с combobox
        self.selection_bar = SelectionBarComponent(page, ComboboxLocators.OBJECT_CLASS_COMBOBOX)

    # =============== МЕТОДЫ ДЕЙСТВИЙ ========================

    def click_add_button(self) -> None:
        """
        Кликает на кнопку 'Добавить'.
        """
        self.toolbar.click_button("add")

    def click_cancel_button(self) -> None:
        """
        Кликает на кнопку 'Отменить'.
        """
        self.toolbar.click_button("cancel")

    def open_object_class_combobox(self) -> None:
        """
        Открывает выпадающий список combobox 'Класс объекта учета'.
        """
        logger.info("Открытие combobox 'Класс объекта учета'...")
        self.selection_bar.open_values_list()

    def select_object_class(self, class_name: str) -> None:
        """
        Выбирает класс объекта из выпадающего списка.

        Args:
            class_name: Название класса объекта для выбора

        Raises:
            AssertionError: Если класс не найден в списке
        """
        logger.info(f"Выбор класса объекта: '{class_name}'...")

        # Открываем combobox
        self.open_object_class_combobox()

        # Выбираем значение из списка
        self.selection_bar.select_value(class_name)

        # Проверяем что выбор произошел
        self.wait_for_timeout(1000)
        selected_value = self.get_selected_object_class()

        if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower():
            # Если выбор не произошел, получаем доступные опции для отладки
            available_options = self.get_object_class_options()
            logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}")
            raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'")

        logger.info(f"Класс объекта '{class_name}' успешно выбран")

    def get_object_class_options(self) -> list[str]:
        """
        Получает список доступных опций из combobox.

        Returns:
            list[str]: Список доступных классов объектов
        """
        logger.info("Получение списка опций combobox 'Класс объекта учета'...")

        available_options = self.selection_bar.get_available_options()

        logger.info(f"Доступные опции класса объекта: {available_options}")
        return available_options

    def get_selected_object_class(self) -> str:
        """
        Получает выбранный класс объекта учета.

        Returns:
            str: Выбранный класс объекта или пустая строка если ничего не выбрано
        """
        # Получаем заголовок панели выбора
        return self.selection_bar.get_selection_bar_title()

    def fill_rack_data(self, name: str, height: str = "42", depth: str = "1000",
                      serial: str = "", inventory: str = "", comment: str = "",
                      cable_entry: str = "", state: str = "", owner: str = "",
                      service_org: str = "", project: str = "") -> None:
        """
        Заполняет данные для создания стойки.

        Args:
            name: Наименование стойки
            height: Высота в юнитах (по умолчанию 42)
            depth: Глубина в мм (по умолчанию 1000)
            serial: Серийный номер
            inventory: Инвентарный номер
            comment: Комментарий
            cable_entry: Ввод кабеля
            state: Состояние
            owner: Владелец
            service_org: Обслуживающая организация
            project: Проект/Титул
        """
        logger.info(f"Заполнение данных стойки: {name}")

        # Заполняем обязательные поля
        name_field = self.page.locator(RackLocators.RACK_NAME_FIELD)
        name_field.fill(name)
        logger.info(f"Заполнено поле 'Имя': {name}")

        self._select_combobox("Высота в юнитах", height)
        logger.info(f"Выбрана высота: {height} юнитов")

        self._select_combobox("Глубина (мм)", depth)
        logger.info(f"Выбрана глубина: {depth} мм")

        # Заполняем опциональные поля
        if serial:
            serial_field = self.page.locator(RackLocators.RACK_SERIAL_FIELD)
            serial_field.fill(serial)
            logger.info(f"Заполнен серийный номер: {serial}")

        if inventory:
            inventory_field = self.page.locator(RackLocators.RACK_INVENTORY_FIELD)
            inventory_field.fill(inventory)
            logger.info(f"Заполнен инвентарный номер: {inventory}")

        if comment:
            comment_field = self.page.locator(RackLocators.RACK_COMMENT_FIELD)
            comment_field.fill(comment)
            logger.info(f"Добавлен комментарий: {comment}")

        # Заполняем дополнительные combobox поля
        if cable_entry:
            self._select_combobox("Ввод кабеля", cable_entry)
            logger.info(f"Выбран ввод кабеля: {cable_entry}")

        if state:
            self._select_combobox("Состояние", state)
            logger.info(f"Выбрано состояние: {state}")

        if owner:
            self._select_combobox("Владелец", owner)
            logger.info(f"Выбран владелец: {owner}")

        if service_org:
            self._select_combobox("Обслуживающая организация", service_org)
            logger.info(f"Выбрана обслуживающая организация: {service_org}")

        if project:
            self._select_combobox("Проект/Титул", project)
            logger.info(f"Выбран проект/титул: {project}")

        logger.info("Данные стойки заполнены")

    def _select_combobox(self, field_name: str, value: str) -> None:
        """
        Выбор значения в combobox.

        Args:
            field_name: Название поля
            value: Значение для выбора
        """
        logger.info(f"Выбор '{value}' в поле '{field_name}'...")

        # Получаем статический локатор из словаря
        if field_name not in COMBOBOX_FIELDS_MAP:
            raise ValueError(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP")

        field_locator = COMBOBOX_FIELDS_MAP[field_name]

        # Для всех полей используем first() чтобы избежать strict mode violation
        field_container = self.page.locator(field_locator).first

        # Прокручиваем до поля
        field_container.scroll_into_view_if_needed()
        self.wait_for_timeout(500)

        # Проверяем видимость поля
        self.base_component.check_visibility(field_container, f"Поле '{field_name}' не найдено")

        # Универсальный клик с force=True для всех полей
        field_container.click(force=True)
        self.wait_for_timeout(1000)

        # Вводим значение
        self.page.keyboard.type(value)
        self.wait_for_timeout(500)
        self.page.keyboard.press("Enter")

        logger.info(f"Поле '{field_name}' заполнено")

    def create_rack(self, rack_name: str, **kwargs) -> None:
        """
        Полный процесс создания стойки.

        Args:
            rack_name: Наименование стойки
            **kwargs: Дополнительные параметры стойки
        """
        logger.info(f"Начало процесса создания стойки: {rack_name}")

        # Выбираем класс объекта "Стойка"
        self.select_object_class("Стойка")
        self.wait_for_timeout(1000)

        # Проверяем наличие полей стойки
        self.check_rack_fields_presence()

        # Заполняем данные
        self.fill_rack_data(rack_name, **kwargs)

        # Создаем стойку
        self.click_add_button()

        logger.info(f"Процесс создания стойки '{rack_name}' завершен")

    def clear_combobox_field(self, field_name: str) -> None:
        """
        Очищает значение в combobox поле с помощью кнопки закрытия (крестика).

        Args:
            field_name: Название поля для очистки
        """
        logger.info(f"Очистка combobox поля '{field_name}' с помощью кнопки закрытия...")

        if field_name not in COMBOBOX_FIELDS_MAP:
            logger.warning(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP")
            return

        field_locator = COMBOBOX_FIELDS_MAP[field_name]

        # Находим поле по локатору
        field_container = self.page.locator(field_locator).first

        # Проверяем что поле видимо
        if not field_container.is_visible():
            logger.info(f"Поле '{field_name}' не видимо, пропускаем очистку")
            return

        # Прокручиваем до поля
        field_container.scroll_into_view_if_needed()
        self.wait_for_timeout(500)

        # Ищем кнопку закрытия (крестик) внутри контейнера поля
        close_button = field_container.locator(ComboboxLocators.COMBOBOX_CLOSE_BUTTON)

        # Проверяем наличие и видимость кнопки закрытия
        if close_button.count() > 0 and close_button.is_visible():
            # Если кнопка закрытия видима - кликаем на нее
            close_button.click()
            self.wait_for_timeout(500)
            logger.info(f"Combobox поле '{field_name}' очищено с помощью кнопки закрытия")
        else:
            # Если кнопки закрытия нет, просто логируем этот факт
            logger.info(f"Кнопка закрытия не найдена для поля '{field_name}', очистка не выполнена")

    def clear_rack_fields(self) -> None:
        """
        Очищает все поля формы создания стойки.
        """
        logger.info("Очистка всех полей формы стойки...")

        # Очищаем текстовые поля
        text_fields = [
            (RackLocators.RACK_NAME_FIELD, "Имя"),
            (RackLocators.RACK_SERIAL_FIELD, "Серийный номер"),
            (RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"),
            (RackLocators.RACK_COMMENT_FIELD, "Комментарий")
        ]

        for field_locator, field_name in text_fields:
            field = self.page.locator(field_locator)
            if field.count() > 0 and field.first.is_visible():
                field.fill("")
                logger.info(f"Текстовое поле '{field_name}' очищено")

        # Очищаем combobox поля
        combobox_fields = [
            "Высота в юнитах",
            "Глубина (мм)",
            "Ввод кабеля",
            "Состояние",
            "Владелец",
            "Обслуживающая организация",
            "Проект/Титул"
        ]

        for field_name in combobox_fields:
            self.clear_combobox_field(field_name)

        logger.info("Все поля формы стойки очищены")

    # =============== МЕТОДЫ ПРОВЕРОК ========================
    def check_rack_exists(self, rack_name: str) -> bool:
        """
        Проверяет, существует ли уже стойка с указанным именем в навигационной панели.

        Args:
            rack_name: Имя стойки для проверки

        Returns:
            bool: True если стойка существует, False если нет
        """
        logger.info(f"Проверка существования стойки с именем '{rack_name}'")

        self.main_page.click_main_navigation_panel_item("Объекты")
        self.main_page.click_main_navigation_panel_item("Объекты")
        self.wait_for_timeout(1000)
        self.main_page.click_subpanel_item("test-zone")
        self.wait_for_timeout(3000)

        nav_panel_locator = NavigationPanelLocators.TREEVIEW

        # Проверяем видимость элемента через is_visible
        element = self.page.locator(nav_panel_locator).get_by_text(rack_name).first

        if element.is_visible():
            logger.info(f"Стойка с именем '{rack_name}' найдена")
            return True
        else:
            logger.info(f"Стойки с именем '{rack_name}' не найдена")
            return False

    def should_be_toolbar_buttons(self) -> None:
        """
        Проверяет наличие и функциональность кнопок тулбара.

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

        self.wait_for_timeout(2000)

        self.toolbar.check_button_visibility("add")
        self.toolbar.check_button_tooltip("add", "Добавить")

        self.toolbar.check_button_visibility("cancel")
        self.toolbar.check_button_tooltip("cancel", "Отменить")
        self.toolbar.click_button("cancel")
        self.wait_for_timeout(2000)

    def check_toolbar_title(self, expected_title: str) -> None:
        """
        Проверяет заголовок тулбара.

        Args:
            expected_title: Ожидаемый заголовок тулбара

        Raises:
            AssertionError: Если заголовок не соответствует ожидаемому
        """
        logger.info(f"Проверка заголовок тулбара: '{expected_title}'...")

        # Используем метод тулбара с фильтрацией по тексту
        actual_text = self.toolbar.get_toolbar_title_text(
            filter_text="Создать дочерний элемент в"
        )
        assert expected_title in actual_text, f"Заголовок не совпадает. Ожидалось: '{expected_title}', Получено: '{actual_text}'"

        logger.info(f"Заголовок тулбара корректен: '{actual_text}'")

    def check_object_class_combobox_presence(self) -> None:
        """
        Проверяет наличие combobox 'Класс объекта учета'.

        Raises:
            AssertionError: Если combobox не найден
        """
        logger.info("Проверка наличия combobox 'Класс объекта учета'...")

        self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не найден")
        logger.info("Combobox 'Класс объекта учета' найден")

    def check_object_class_combobox_content(self) -> None:
        """
        Проверяет содержимое combobox 'Класс объекта учета'.

        Raises:
            AssertionError: Если содержимое не соответствует ожидаемому
        """
        logger.info("Проверка содержимого combobox 'Класс объекта учета'...")

        combobox_locator = self.page.locator(ComboboxLocators.OBJECT_CLASS_COMBOBOX)

        # Проверяем что combobox видим
        self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не виден")

        # Проверяем наличие label
        label_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_LABEL)
        expect(label_locator).to_have_text("Класс объекта учета")

        # Проверяем наличие input поля
        input_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_INPUT)
        self.base_component.check_visibility(input_locator, "Input поле combobox не найдено")

        # Для combobox нормально иметь readonly атрибут - это стандартное поведение
        # Проверяем что поле доступно для выбора (не disabled)
        expect(input_locator).not_to_have_attribute("disabled", "disabled")

        # Проверяем наличие иконки стрелки
        icon_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_ICON_ARROW)
        self.base_component.check_visibility(icon_locator, "Иконка стрелки combobox не найдена")

        logger.info("Содержимое combobox 'Класс объекта учета' корректно")

    def check_object_class_selected(self, expected_class: str) -> None:
        """
        Проверяет что выбран указанный класс объекта.

        Args:
            expected_class: Ожидаемый выбранный класс объекта

        Raises:
            AssertionError: Если выбранный класс не соответствует ожидаемому
        """
        logger.info(f"Проверка выбранного класса объекта: '{expected_class}'...")

        # Даем время на обновление значения
        self.wait_for_timeout(1000)

        actual_class = self.get_selected_object_class()

        # Проверка - допускаем частичное совпадение
        if expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower():
            logger.info(f"Класс объекта '{expected_class}' успешно выбран (фактически: '{actual_class}')")
        else:
            raise AssertionError(f"Выбранный класс не соответствует ожидаемому. Ожидалось: '{expected_class}', Получено: '{actual_class}'")

    def check_object_class_options_content(self, expected_options: list = None) -> None:
        """
        Проверяет содержимое списка опций combobox.

        Args:
            expected_options: Ожидаемый список опций. Если None, проверяет только что список не пустой.

        Raises:
            AssertionError: Если список опций не соответствует ожидаемому
        """
        logger.info("Проверка содержимого списка опций combobox...")

        # Получаем доступные опции
        available_options = self.get_object_class_options()

        if expected_options is not None:
            # Проверяем соответствие ожидаемому списку
            assert set(available_options) == set(expected_options), (
                f"Список опций не соответствует ожидаемому. "
                f"Ожидалось: {expected_options}, Получено: {available_options}"
            )
        else:
            # Проверяем что список не пустой
            assert len(available_options) > 0, "Список опций combobox пустой"

        logger.info(f"Содержимое списка опций корректно: {available_options}")

    def check_dropdown_item_presence(self, item_text: str) -> None:
        """
        Проверяет наличие элемента в выпадающем списке.

        Args:
            item_text: Текст элемента для проверки
        """
        logger.info(f"Проверка наличия элемента '{item_text}' в выпадающем списке...")

        # Получаем все опции и проверяем наличие
        available_options = self.get_object_class_options()

        if item_text not in available_options:
            raise AssertionError(f"Элемент '{item_text}' не найден в списке опций. Доступные опции: {available_options}")

        logger.info(f"Элемент '{item_text}' присутствует в списке")

    def check_rack_fields_presence(self) -> None:
        """
        Проверяет наличие полей специфичных для стойки.

        Raises:
            AssertionError: Если какое-либо поле не найдено
        """
        logger.info("Проверка наличия полей для стойки...")

        # Основные обязательные поля
        required_fields = [
            (RackLocators.RACK_NAME_FIELD, "Имя"),
            (RackLocators.RACK_HEIGHT_FIELD, "Высота в юнитах"),
            (RackLocators.RACK_DEPTH_FIELD, "Глубина (мм)")
        ]

        # Дополнительные поля
        optional_fields = [
            (RackLocators.RACK_SERIAL_FIELD, "Серийный номер"),
            (RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"),
            (RackLocators.RACK_COMMENT_FIELD, "Комментарий"),
            (RackLocators.RACK_CABLE_ENTRY_FIELD, "Ввод кабеля"),
            (RackLocators.RACK_STATE_FIELD, "Состояние"),
            (RackLocators.RACK_OWNER_FIELD, "Владелец"),
            (RackLocators.RACK_SERVICE_ORG_FIELD, "Обслуживающая организация"),
            (RackLocators.RACK_PROJECT_FIELD, "Проект/Титул")
        ]

        # Проверяем обязательные поля
        for field_locator, field_name in required_fields:
            self.base_component.check_visibility(field_locator, f"Обязательное поле '{field_name}' не найдено")
            logger.info(f"Обязательное поле '{field_name}' найдено")

        # Проверяем дополнительные поля
        for field_locator, field_name in optional_fields:
            field = self.page.locator(field_locator)
            if field.count() > 0 and field.first.is_visible():
                logger.info(f"Дополнительное поле '{field_name}' найдено")
            else:
                logger.info(f"Дополнительное поле '{field_name}' не найдено или не отображается")

        logger.info("Все основные поля для стойки присутствуют")

    def check_field_highlighted_error(self, field_name: str) -> None:
        """
        Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).

        Args:
            field_name: Название поля для проверки
        """
        logger.info(f"Проверка подсветки поля '{field_name}' цветом ошибки...")

        # Локаторы только для обязательных полей
        required_fields = {
            "Имя": RackLocators.RACK_NAME_FIELD,
            "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
            "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
        }

        if field_name not in required_fields:
            raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается")

        field_locator = required_fields[field_name]
        field_element = self.page.locator(field_locator)

        # Проверяем что поле видимо
        self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено")

        # Ищем родительский контейнер с использованием константы
        parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first

        # Проверка классов ошибки
        if parent_container.count() > 0:
            error_classes = AlertLocators.ERROR_CLASSES

            is_error_highlighted = False
            for error_class in error_classes:
                error_element = parent_container.locator(f".{error_class}")
                if error_element.count() > 0:
                    is_error_highlighted = True
                    logger.info(f"Поле '{field_name}' подсвечено ошибкой")
                    break

            if not is_error_highlighted:
                raise AssertionError(f"Поле '{field_name}' не подсвечено цветом ошибки ")

        logger.info(f"Поле '{field_name}' корректно подсвечено цветом ошибки")

    def check_field_not_highlighted_error(self, field_name: str) -> None:
        """
        Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).

        Args:
            field_name: Название поля для проверки
        """
        logger.info(f"Проверка отсутствия подсветки ошибки у поля '{field_name}'...")

        # Локаторы только для обязательных полей
        required_fields = {
            "Имя": RackLocators.RACK_NAME_FIELD,
            "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
            "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
        }

        if field_name not in required_fields:
            raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается")

        field_locator = required_fields[field_name]
        field_element = self.page.locator(field_locator)

        # Проверяем что поле видимо
        self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено")

        # Ищем родительский контейнер с использованием константы
        parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first

        # Поверка отсутствия классов ошибки
        if parent_container.count() > 0:
            error_classes = AlertLocators.ERROR_CLASSES

            for error_class in error_classes:
                error_element = parent_container.locator(f".{error_class}")
                if error_element.count() > 0:
                    raise AssertionError(f"Поле '{field_name}' подсвечено ошибкой")

        logger.info(f"Поле '{field_name}' корректно не подсвечено цветом ошибки")
