from typing import List, Dict

from loguru import logger
from selenium.webdriver.common.by import By

from selenium_tutorial.helper.selenium_driver import SeleniumDriver
from selenium_tutorial.helper.web_element import SeleniumWebElement

log_message = "why={reason}, how={action}"


class Crawler:
    def __init__(self, start_url: str):
        self.start_url = start_url
        self.unvisited_urls = [self.start_url]
        self.visited_urls = []
        logger.info(log_message, reason="Initialization", action="Set start_url to " + self.start_url)

    def get_url_count(self) -> Dict[str, int]:
        count = {}
        while self.unvisited_urls:
            urls = self.get_new_urls()
            urls_to_visit = self.filter_urls_to_visit(urls)
            self.unvisited_urls.extend(urls_to_visit)
            for url in urls:
                if url in count:
                    count[url] = count[url] + 1
                else:
                    count[url] = 1
            break
        logger.info(log_message, reason="Notice", action="Visited urls: " + str(self.visited_urls))
        logger.info(log_message, reason="Notice", action="Url count: " + str(count))
        return count

    def get_new_urls(self) -> List[str]:
        with SeleniumDriver() as driver:
            driver.open_url(self.get_unvisited_url())
            elements = driver.get_elements_by_query(By.TAG_NAME, "a")
            urls = self.get_urls_from_elements(elements)
        logger.info(log_message,
                    reason="Expanding the search space", action="Found new urls: " + str(urls)
                    )
        return urls

    def get_unvisited_url(self) -> str:
        url = self.unvisited_urls.pop()
        self.visited_urls.append(url)
        logger.info(log_message, reason="Search", action="Next url is " + url)
        return url

    def get_urls_from_elements(self, elements: List[SeleniumWebElement]) -> List[str]:
        hrefs = self.get_hrefs_from_elements(elements)
        return self.filter_out_non_links(hrefs)

    def get_hrefs_from_elements(self, elements: List[SeleniumWebElement]) -> List[str]:
        return [
            x.get_attribute("href")
            for x in elements
            if x.get_attribute("href")
        ]

    def filter_out_non_links(self, urls: List[str]) -> List[str]:
        return [x for x in urls if x is not None if ("http" in x) or ("file" in x)]

    def filter_urls_to_visit(self, urls: List[str]) -> List[str]:
        unvisited_urls = self.filter_visited_urls(urls)
        deduplicated_urls = self.filter_unvisited_duplicate_urls(unvisited_urls)
        domain_urls = self.filter_outisde_domain_urls(deduplicated_urls)
        return domain_urls

    def filter_visited_urls(self, urls: List[str]) -> List[str]:
        return [x for x in urls if not (x in self.visited_urls)]

    def filter_unvisited_duplicate_urls(self, urls: List[str]) -> List[str]:
        return [x for x in urls if not (x in self.unvisited_urls)]

    def filter_outisde_domain_urls(self, urls: List[str]) -> List[str]:
        return [x for x in urls if self.start_url in x]

from typing import List, Dict

from loguru import logger
from selenium.webdriver.common.by import By

from selenium_tutorial.helper.selenium_driver import SeleniumDriver
from selenium_tutorial.helper.web_element import SeleniumWebElement

log_message = "why={reason}, how={action}"


class Crawler:
    def __init__(self, start_url: str):
        self.start_url = start_url
        self.unvisited_urls = [self.start_url]
        self.visited_urls = []
        logger.info(log_message, reason="Initialization", action="Set start_url to " + self.start_url)

    def get_url_count(self) -> Dict[str, int]:
        count = {}
        while self.unvisited_urls:
            urls = self.get_new_urls()
            urls_to_visit = self.filter_urls_to_visit(urls)
            self.unvisited_urls.extend(urls_to_visit)
            for url in urls:
                if url in count:
                    count[url] = count[url] + 1
                else:
                    count[url] = 1
            break
        logger.info(log_message, reason="Notice", action="Visited urls: " + str(self.visited_urls))
        logger.info(log_message, reason="Notice", action="Url count: " + str(count))
        return count

    def get_new_urls(self) -> List[str]:
        with SeleniumDriver() as driver:
            driver.open_url(self.get_unvisited_url())
            elements = driver.get_elements_by_query(By.TAG_NAME, "a")
            urls = self.get_urls_from_elements(elements)
        logger.info(log_message,
                    reason="Expanding the search space", action="Found new urls: " + str(urls)
                    )
        return urls

    def get_unvisited_url(self) -> str:
        url = self.unvisited_urls.pop()
        self.visited_urls.append(url)
        logger.info(log_message, reason="Search", action="Next url is " + url)
        return url

    def get_urls_from_elements(self, elements: List[SeleniumWebElement]) -> List[str]:
        hrefs = self.get_hrefs_from_elements(elements)
        return self.filter_out_non_links(hrefs)

    def get_hrefs_from_elements(self, elements: List[SeleniumWebElement]) -> List[str]:
        return [
            x.get_attribute("href")
            for x in elements
            if x.get_attribute("href")
        ]

    def filter_out_non_links(self, urls: List[str]) -> List[str]:
        return [x for x in urls if x is not None if ("http" in x) or ("file" in x)]

    def filter_urls_to_visit(self, urls: List[str]) -> List[str]:
        unvisited_urls = self.filter_visited_urls(urls)
        deduplicated_urls = self.filter_unvisited_duplicate_urls(unvisited_urls)
        domain_urls = self.filter_outisde_domain_urls(deduplicated_urls)
        return domain_urls

    def filter_visited_urls(self, urls: List[str]) -> List[str]:
        return [x for x in urls if not (x in self.visited_urls)]

    def filter_unvisited_duplicate_urls(self, urls: List[str]) -> List[str]:
        return [x for x in urls if not (x in self.unvisited_urls)]

    def filter_outisde_domain_urls(self, urls: List[str]) -> List[str]:
        return [x for x in urls if self.start_url in x]
