"""Screenshot TestCase."""

import logging
import os.path
import time
from typing import Tuple
from urllib.parse import urlparse

import allure
import pytest
from PIL import Image
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

from screenshot_tests.image_proccessing.image_processor import ImageProcessor
from screenshot_tests.utils import common


# noinspection PyAttributeOutsideInit
# аннотируем все классы всех скриншот тестов для работы плагина
# https://github.com/allure-framework/allure2/tree/master/plugins/screen-diff-plugin
@allure.label('testType', 'screenshotDiff')
class TestCase(common.TestCase):
    """Screenshot TestCase."""

    # Для мобильных устройств и хрома в режиме эмуляции плотность пикселей будет отличаться.
    pixel_ratio = 1

    @pytest.fixture(autouse=True)
    def screenshot_prepare(self):
        self.image_processor = ImageProcessor()

    def _scroll(self, x: int, y: int):
        scroll_string = f"window.scrollTo({x}, {y})"
        self.driver.execute_script(scroll_string)
        time.sleep(0.2)
        logging.info(f"Scroll to «{scroll_string}»")

    def _make_screenshot_whole_page(self, locator_type, query_string):
        scroll_time = 0.2

        # Нужно заставить отработать все что есть с автоподгрузкой, чтобы получить настоящую длину страницы
        x, y, width, height = self._get_raw_coords_by_locator(locator_type, query_string)
        total_height = self.driver.execute_script("return document.body.parentNode.scrollHeight")
        logging.info(f"total height: {total_height}")

        while True:
            old_total_height = total_height
            self._scroll(0, total_height + 9999)
            time.sleep(scroll_time)
            total_height = self.driver.execute_script("return document.body.parentNode.scrollHeight")
            logging.info(f"new total height: {total_height}")
            logging.info(f"y: {y}")
            # Если высота перестала изменяться, или элемент уже попал на скриншот.
            # Второе условие позволяет не скролить до конца на стрницах с "бесконечной" длинной (выдача видео, картинок)
            if (old_total_height == total_height) or (total_height > y):
                break

        total_width = self.driver.execute_script("return document.body.offsetWidth")
        viewport_width = self.driver.execute_script("return document.body.clientWidth")
        viewport_height = self.driver.execute_script("return window.innerHeight")
        screenshots = []
        offset = 0
        assert viewport_width == total_width, "Ширина вьюпорта, и ширина экрана должны совпадать"

        self._scroll(0, 0)
        while offset <= total_height or offset <= y:
            logging.info(f"offset: {offset}, total height: {total_height}")
            screenshots.append(self.driver.get_screenshot_as_png())
            offset += viewport_height
            self._scroll(0, offset)

        # эта часть последнего скриншота, которая дублирует предпоследний скриншот
        # так просходит потому что не всегда страница делится на целое количество вьюпортов
        over_height = offset - total_height
        logging.info(f"offset: {offset}, total height: {total_height}, over height: {over_height}, pixel density: {self.pixel_ratio}")
        return self.image_processor.paste(screenshots, over_height * self.pixel_ratio)

    def _use_full_screen(self):
        # хак чтобы снять целиком элемент который не помещается на страницу
        # https://stackoverflow.com/questions/44085722/how-to-get-screenshot-of-full-webpage-using-selenium-and-java
        # https://gist.github.com/elcamino/5f562564ecd2fb86f559
        self.driver.set_window_size(1425, 2900)

    def _get_raw_coords_by_locator(self, locator_type, query_string):
        """Без учета плотности пикселей."""
        wait = WebDriverWait(self.driver, timeout=10, ignored_exceptions=Exception)
        wait.until(lambda _: self.driver.find_element(locator_type, query_string).is_displayed(),
                   message="Невозможно получить размеры элемента, элемент не отображается")
        # После того, как дождались видимости элемента, ждем еще 2 секунды, чтобы точно завершились разные анимации
        time.sleep(2)
        el = self.driver.find_element(locator_type, query_string)
        location = el.location
        size = el.size
        x = location["x"]
        y = location["y"]
        width = location["x"] + size['width']
        height = location["y"] + size['height']
        if width == 0:
            width = 1920
        if height == 0:
            height = 1080
        # (312, 691, 1112, 691)
        return x, y, width, height

    def _get_coords_by_locator(self, locator_type, query_string) -> Tuple[int, int, int, int]:
        x, y, width, height = self._get_raw_coords_by_locator(locator_type, query_string)
        return x * self.pixel_ratio, y * self.pixel_ratio, width * self.pixel_ratio, height * self.pixel_ratio

    def _get_element_screenshot(
        self,
        locator_type,
        query_string,
        action,
        finalize,
        scroll_and_screen
    ) -> Tuple[Image.Image, Tuple[int, int, int, int]]:
        """Сделать скриншот страницы и кропнуть до скриншота элемента.

        Не получится использовать метод session/{sessionId}/element/{elementId}/screenshot
        Потому что он имплементирован только в эдж.
        https://stackoverflow.com/questions/36084257/im-trying-to-take-a-screenshot-of-an-element-with-selenium-webdriver-but-unsup
        """
        if not scroll_and_screen:
            # Иногда страница по дефолту открыта посередине, чтобы не ползли координаты
            # элемента с scroll_and_screen=False, скролим до начала. Это нужно делать до вызова action, на случай если
            # в action страницу нужно проскролить до определенной точки.
            self._scroll(0, 0)

        # Тут готовим страницу к снятию скриншота
        if callable(action):
            action()

        if scroll_and_screen:
            screen = self._make_screenshot_whole_page(locator_type, query_string)
        else:
            screen = self.image_processor.load_image_from_bytes(self.driver.get_screenshot_as_png())

        coordinates = self._get_coords_by_locator(locator_type, query_string)
        logging.info(f"element: {query_string}, coordinates: {coordinates}")

        # Тут можно выполнить дополнительные проверки после снятия скрина
        if callable(finalize):
            finalize()

        return screen.crop(coordinates), coordinates

    def _get_diff(self, element=None, action=None, full_screen=True, full_page=False, finalize=None, scroll_and_screen=True):
        """Получит скриншоты с текущей страницы, и с эталонной.

        Поблочно сравнит их, и вернет количество отличающихся блоков.
        :param element: tuple с типом локатора и локатором.
        :param action: функция, которая подготовит страницу к снятию скриншота.
        :param full_screen: resize ли браузер до максимума.
        :param full_page: скринить всю страницу, а не только переданный элемент.
        :param finalize: финализация после сравнения скриншотов.
        :param scroll_and_screen: скролить страницу (сверху к низу) и склеивать участки в один скриншот.
        """
        if full_screen:
            self._use_full_screen()

        if full_page:
            locator_type, query_string = (By.XPATH, "//body")
            scroll_and_screen = False
        else:
            locator_type, query_string = element[0], element[1]

        saved_url = urlparse(self.driver.current_url)
        # noinspection PyProtectedMember
        prod_url = saved_url._replace(netloc=self.staging)

        # На текущей странице делаем первый скриншот
        first_image, coords_test = self._get_element_screenshot(locator_type, query_string, action, finalize, scroll_and_screen)
        logging.info('Done screen on test stand')
        # Теперь делаем скриншот в проде
        self.driver.get(prod_url.geturl())
        second_image, coords_prod = self._get_element_screenshot(locator_type, query_string, action, finalize, scroll_and_screen)
        logging.info('Done screen on stage stand')

        # Возращаемся на тестовый стенд. Всегда нужно возвращаться на тестовый стенд. На это завязаны тесты и отчеты
        self.driver.get(saved_url.geturl())

        # Для добавления в отчет (https://github.com/allure-framework/allure2/tree/master/plugins/screen-diff-plugin)
        # noinspection PyUnboundLocalVariable
        allure.attach(self.image_processor.image_to_bytes(first_image), 'actual', allure.attachment_type.PNG)
        # noinspection PyUnboundLocalVariable
        allure.attach(self.image_processor.image_to_bytes(second_image), 'expected', allure.attachment_type.PNG)

        # noinspection PyUnboundLocalVariable
        diff, result = self.image_processor.get_images_diff(first_image, second_image)
        allure.attach(result, 'diff', allure.attachment_type.PNG)

        return diff, saved_url, prod_url

    def _get_screenshot_diff(self, element=None, screenshot_path=None, action=None, full_screen=True,
                             full_page=False, finalize=None, scroll_and_screen=True):
        """Получит скриншот с текущей страницы, поблочно сравнит с эталонным и вернет количество отличающихся блоков.
        :param screenshot_path: путь к файлу с изображением.
        :param element: tuple с типом локатора и локатором.
        :param action: функция, которая подготовит страницу к снятию скриншота.
        :param full_screen: resize ли браузер до максимума.
        :param full_page: скринить всю страницу, а не только переданный элемент.
        :param finalize: финализация после сравнения скриншотов.
        :param scroll_and_screen: скролить страницу (сверху к низу) и склеивать участки в один скриншот.
        """
        if full_screen:
            self._use_full_screen()

        if full_page:
            locator_type, query_string = (By.XPATH, "//body")
            scroll_and_screen = False
        else:
            locator_type, query_string = element[0], element[1]

        saved_url = urlparse(self.driver.current_url)
        test_url = self.base_url

        self.driver.get(test_url)
        test_image, coords_prod = self._get_element_screenshot(locator_type, query_string, action, finalize, scroll_and_screen)
        logging.info('Done screen on test stand')

        # Возращаемся на тестовый стенд. Всегда нужно возвращаться на тестовый стенд. На это завязаны тесты и отчеты
        self.driver.get(saved_url.geturl())

        second_image = Image.open(screenshot_path, formats=['PNG'])

        # Для добавления в отчет (https://github.com/allure-framework/allure2/tree/master/plugins/screen-diff-plugin)
        # noinspection PyUnboundLocalVariable
        allure.attach(self.image_processor.image_to_bytes(test_image), 'actual', allure.attachment_type.PNG)
        # noinspection PyUnboundLocalVariable
        allure.attach(self.image_processor.image_to_bytes(second_image), 'expected', allure.attachment_type.PNG)

        # noinspection PyUnboundLocalVariable
        diff, result = self.image_processor.get_images_diff(test_image, second_image)
        allure.attach(result, 'diff', allure.attachment_type.PNG)

        return diff, test_url

    def get_diff(self, *args, **kwargs):
        diff, _, _ = self._get_diff(*args, **kwargs)
        return diff

    def check_by_screenshot(self, element, *args, **kwargs):
        diff, saved_url, prod_url = self._get_diff(element, *args, **kwargs)
        assert diff == 0, f"Элемент отличается на страницах:\n{saved_url.geturl()}\nи\n{prod_url.geturl()}"

    def check_by_screenshot_file(self, element, *args, **kwargs):
        diff, test_url = self._get_screenshot_diff(element, *args, **kwargs)
        assert diff == 0, f"Элемент отличается на странице:\n{test_url}\n"

    def save_screenshot(self, screenshot_path, element=None, action=None, full_screen=True, full_page=False, finalize=None, scroll_and_screen=True):
        if full_screen:
            self._use_full_screen()

        if full_page:
            locator_type, query_string = (By.XPATH, "//body")
            scroll_and_screen = False
        else:
            locator_type, query_string = element[0], element[1]

        saved_url = urlparse(self.driver.current_url)
        test_url = self.base_url

        self.driver.get(test_url)
        test_image, coords_prod = self._get_element_screenshot(locator_type, query_string, action, finalize, scroll_and_screen)

        self.driver.get(saved_url.geturl())

        test_image.save(screenshot_path, format='png')

        return coords_prod
