"""Модуль компонента таблицы. Содержит класс для работы с табличными данными."""

from datetime import datetime
from playwright.sync_api import Page, expect, Locator
from tools.logger import get_logger
from components.base_component import BaseComponent


logger = get_logger("TABLE")


class TableComponent(BaseComponent):
    """Компонент таблицы. Предоставляет методы для работы с табличными данными."""

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

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

        super().__init__(page)

    # Действия:
    def click_arrow_button(self, table_locator: str | Locator, index: int) -> None:
        """ Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

        Args:
            table_locator: Локатор таблицы.
            index: Индекс ячейки в заголовке.
        """

        arrow_button = self.get_header_cell_button(table_locator, index)
        assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"
        arrow_button.click()

    def datetime2timestamp(self, date_string: str) -> float|None:
        """ Конвертация строкового представления даты и времени в Unix timestamp
        Args:
            date_string: Строка с датой и временем в формате d.m.Y H:M:S.

        Returns:
            float: Unix timestamp.
            None: конвертация невозможна
        """

        # Формат, соответствующий строке с датой и временем
        format_string = "%d.%m.%Y %H:%M:%S"

        try:
            date_object = datetime.strptime(date_string, format_string)
            return date_object.timestamp()
        except ValueError:
            return None

    def get_arrow_button_state(self, table_locator: str | Locator, index: int) -> str:
        """ Получение состояния кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

        Args:
            table_locator: Локатор таблицы.
            index: Индекс ячейки в заголовке.

        Returns:
            up, если это стрелочка вверх. down, если это стрелочка вниз.
        """

        arrow_button = self.get_header_cell_button(table_locator, index)
        assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"

        state = arrow_button.inner_text()
        if state == "keyboard_arrow_up":
            return "up"
        elif state == "keyboard_arrow_down":
            return "down"
        else:
            assert False, f"Got unsupported arrow state: {state}"

    def get_header_cell_button(self, table_locator: str | Locator, index: int) -> Locator:
        """ Поиск кнопки в ячейке заголовка таблицы

        Args:
            table_locator: Локатор таблицы.
            index: Индекс ячейки в заголовке.

        Returns:
            Локатор строки кнопки.

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

        table = self.get_locator(table_locator)
        header_cells_count = table.locator("//thead/tr/th").count()
        assert index in range(header_cells_count), "Header cell index is out of range"
        return table.locator("//thead/tr/th").nth(index).get_by_role("button")

    def get_row_locator(self, table_locator: str | Locator, row_index: int) -> Locator | None:
        """Возвращает локатор строки по индексу.

        Args:
            table_locator: Локатор таблицы.
            row_index: Индекс строки.

        Returns:
            Локатор строки или None, если индекс вне диапазона.
        """

        table = self.get_locator(table_locator)

        rows = table.locator("//tbody/tr")

        if row_index in range(rows.count()):
            return rows.nth(row_index)
        else:
            return None

    def get_rows_count(self, locator: str | Locator) -> int:
        """Возвращает количество строк в таблице (без заголовка).

        Returns:
            int: Количество строк с данными.

        Raises:
            AssertionError: Если таблица пуста.
        """

        table_content = self.read(locator)
        rows_count = len(table_content)

        if rows_count == 0:
            assert False, "The contents of the table are missing"

        return rows_count - 1


    def read(self, locator: str | Locator) -> list[list[str]]:
        """Читает данные таблицы, включая заголовки.

        Args:
            locator: Локатор таблицы.

        Returns:
            Двумерный список с данными таблицы.
        """

        table_data = []
        table = self.get_locator(locator)

        # Чтение заголовка таблицы
        header_cells = table.locator("//thead/tr")
        header_cell_text = header_cells.nth(0).inner_text()
        header_data = header_cell_text.split('\n')
        table_data.append(header_data)

        # Чтение ячеек таблицы
        rows = table.locator("//tbody/tr")
        for i in range(rows.count()):
            row = rows.nth(i)
            cells = row.locator("td")
            row_data = []
            for j in range(cells.count()):
                cell_text = cells.nth(j).inner_text()
                row_data.append(cell_text)
            table_data.append(row_data)

        return table_data

    # Проверки:
    def check_table_headers(self, actual_headers, expected_headers) -> None:
        """ Проверка соответствия заголовка таблицы ожидаемому"""

        is_equals = True

        arrow_state = ["keyboard_arrow_down", "keyboard_arrow_up"]

        for item in actual_headers:
            item = item.strip()

            if item in arrow_state:
                continue

            if item not in expected_headers:
                is_equals = False

        assert is_equals, \
            f"Expected events table headers {expected_headers} are not equal {actual_headers}"

    def check_content(self,
                    locator: str | Locator,
                    expected_headers: list[str],
                    check_table_not_empty: bool = True) -> None:
        """Проверяет содержимое таблицы.

        Проверяет заголовки и наличие данных в таблице.

        Args:
            locator: Локатор таблицы.
            expected_headers: Список ожидаемых заголовков таблицы.
            check_table_not_empty: Флаг проверки, что таблица не пустая.
                                По умолчанию True.

        Raises:
            AssertionError: Если таблица пуста (при check_table_not_empty=True)
                        или заголовки неверны.
        """

        table_content = self.read(locator)

        if len(table_content) == 0:
            assert False, "The contents of the table are missing"

        # Проверка заголовков таблицы
        self.check_table_headers(table_content[0], expected_headers)

        # Проверка наличия данных в таблице
        if len(table_content) == 1:
            if check_table_not_empty:
                assert False, "Table body is missing"
            else:
                logger.info("Таблица пустая (не содержит строк с данными)")

    def check_column_descending_order(self,
                                         locator: str | Locator,
                                         index: int,
                                         convert2timestamp=False) -> bool:
        """Проверка, что заданный столбец таблицы упорядочен по убыванию.

        Args:
            locator: Локатор таблицы.
            index: Индекс столбца.
            convert2timestamp: Конвертировать строковое представление даты и времени в Unix timestamp

        Returns:
            True, если столбец таблицы упорядочен по убыванию. Иначе: False
        """

        table_content = self.read(locator)

        if len(table_content) == 0:
            assert False, "The contents of the table are missing"

        del table_content[0]

        assert index in range(len(table_content[0])), \
                            "Column index is out of range"
        column = []
        for i in range(len(table_content)):
            if convert2timestamp:
                timestamp = self.datetime2timestamp(table_content[i][index])
                assert timestamp, f"Error conversation to timestamp for {table_content[i][index]}"
                column.append(timestamp)
            else:
                column.append(table_content[i][index])

        return all(column[i] >= column[i+1] for i in range(len(column) - 1))

    def check_first_row_visibility(self, locator: str | Locator) -> None:
        """Проверяет видимость первой строки таблицы.

        Args:
            locator: Локатор таблицы.
        """

        table = self.get_locator(locator)
        first_row = table.locator("//tbody/tr").first
        expect(first_row).to_be_visible(), "The first table row is not visible"

    def check_last_row_visibility(self, locator: str | Locator) -> None:
        """Проверяет видимость последней строки таблицы.

        Args:
            locator: Локатор таблицы.
        """

        table = self.get_locator(locator)
        last_row = table.locator("//tbody/tr").last
        expect(last_row).to_be_visible(), "The last table row is not visible"

    def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None:
        """Проверяет изменение цвета строки при наведении.

        Args:
            locator: Локатор таблицы.
            row_index: Индекс проверяемой строки.
        """

        table = self.get_locator(locator)
        row = table.locator("//tbody/tr").nth(row_index)

        row.scroll_into_view_if_needed()
        hover_element = row.locator(".body-row-hover")
        initial_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")

        row.hover()
        self.page.wait_for_timeout(300)

        new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
        assert initial_color != new_color, "Color of row did not change when hovering the cursor"
