import logging
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains

logger = logging.getLogger(__name__)


class DashCoreComponentsMixin(object):
    def select_date_single(self, compid, index=0, day="", outside_month=False):
        """Select Date in DPS component with either index or day
        compid: the id defined for component
        index: the index for all visibles in the popup calendar
        day: a number or string; if set, use this to select instead of index
        outside_month: used in conjunction with day. indicates if the day out
            the scope of current month. default False.
        """
        date = self.find_element(f"#{compid}")
        date.click()

        def is_month_valid(elem):
            classes = elem.get_attribute("class") or ""
            return (
                "dash-datepicker-calendar-date-outside" in classes
                if outside_month
                else "dash-datepicker-calendar-date-outside" not in classes
            )

        self._wait_until_day_is_clickable()
        days = self.find_elements(self.date_picker_day_locator)
        if day:
            filtered = [_ for _ in days if _.text == str(day) and is_month_valid(_)]
            if not filtered or len(filtered) > 1:
                logger.error(
                    "cannot find the matched day with index=%s, day=%s",
                    index,
                    day,
                )
            matched = filtered[0]
        else:
            matched = days[index]

        matched.click()
        time.sleep(0.1)
        return date.get_attribute("value")

    def select_date_range(self, compid, day_range, start_first=True):
        """Select Date in DPR component with a day_range tuple
        compid: the id defined for component
        day_range: a tuple or list, defines the start and end date you want to
            select, the tuple must be length of 1 or 2, i.e.
            (start, ) or (start, end)
        start_first: boolean value decides clicking start or end date.
            default True
        """

        if (
            not day_range
            or not isinstance(day_range, (tuple, list))
            or not set(range(1, 32)).issuperset(day_range)
            or len(day_range) > 2
        ):
            logger.error(
                "data_range is provided with an invalid value %s\n"
                "the accepted range is range(1, 32)",
                day_range,
            )
            return

        if not start_first:
            compid += "-end-date"
        date = self.find_element(f"#{compid}")
        date.click()
        for day in day_range:
            self._wait_until_day_is_clickable()
            matched = [
                _
                for _ in self.find_elements(self.date_picker_day_locator)
                if _.find_element(By.CSS_SELECTOR, "span").text == str(day)
            ]
            matched[0].click()

        return self.get_date_range(compid)

    def get_date_range(self, compid):
        return tuple(
            _.get_attribute("value")
            for _ in self.find_elements(f"#{compid}-wrapper .dash-datepicker-input")
        )

    def _wait_until_day_is_clickable(self, timeout=1):
        WebDriverWait(self.driver, timeout).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, self.date_picker_day_locator))
        )

    @property
    def date_picker_day_locator(self):
        return ".dash-datepicker-calendar-date-inside, .dash-datepicker-calendar-date-outside"

    def click_and_hold_at_coord_fractions(self, elem_or_selector, fx, fy):
        elem = self._get_element(elem_or_selector)

        ActionChains(self.driver).move_to_element_with_offset(
            elem, elem.size["width"] * fx, elem.size["height"] * fy
        ).click_and_hold().perform()

    def move_to_coord_fractions(self, elem_or_selector, fx, fy):
        elem = self._get_element(elem_or_selector)

        ActionChains(self.driver).click_and_hold().move_to_element_with_offset(
            elem, elem.size["width"] * fx, elem.size["height"] * fy
        ).perform()

    def release(self):
        ActionChains(self.driver).release().perform()

    def click_and_drag_at_coord_fractions(self, elem_or_selector, fx1, fy1, fx2, fy2):
        elem = self._get_element(elem_or_selector)

        ActionChains(self.driver).move_to_element_with_offset(
            elem, elem.size["width"] * fx1, elem.size["height"] * fy1
        ).click_and_hold().move_to_element_with_offset(
            elem, elem.size["width"] * fx2, elem.size["height"] * fy2
        ).release().perform()
