from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urljoin, urlparse

from bs4 import BeautifulSoup

from newsreader.news.collection.base import Builder, Client, Collector, Stream
from newsreader.news.collection.exceptions import StreamException, StreamParseException
from newsreader.news.collection.feed import FeedClient
from newsreader.news.collection.utils import fetch


LINK_RELS = [
    "icon",
    "shortcut icon",
    "apple-touch-icon",
    "apple-touch-icon-precomposed",
]


class WebsiteStream(Stream):
    def read(self):
        response = fetch(self.rule.website_url)

        return self.parse(response.content), self

    def parse(self, payload):
        try:
            return BeautifulSoup(payload, features="lxml")
        except TypeError:
            raise StreamParseException("Could not parse given HTML")


class WebsiteURLBuilder(Builder):
    def build(self):
        try:
            url = self.payload["feed"]["link"]
        except (KeyError, TypeError):
            url = None

        self.instances = [(self.stream, url)] if url else []

    def save(self):
        for stream, url in self.instances:
            stream.rule.website_url = url
            stream.rule.save()


class FaviconBuilder(Builder):
    def build(self):
        rule = self.stream.rule

        url = self.parse()

        self.instances = [(rule, url)] if url else []

    def parse(self):
        soup = self.payload

        if not soup.head:
            return

        links = soup.head.find_all("link")
        url = self.parse_links(links)

        if not url:
            return

        parsed_url = urlparse(url)

        if not parsed_url.scheme and not parsed_url.netloc:
            if not self.stream.rule.website_url:
                return
            return urljoin(self.stream.rule.website_url, url)
        elif not parsed_url.scheme:
            return urljoin(f"https://{parsed_url.netloc}", parsed_url.path)

        return url

    def parse_links(self, links):
        favicons = set()
        icons = set()

        for link in links:
            if "href" not in link.attrs:
                continue

            if "favicon" in link["href"]:
                favicons.add(link["href"].lower())

            if "rel" in link.attrs:
                for rel in link["rel"]:
                    if rel in LINK_RELS:
                        icons.add(link["href"].lower())

        if favicons:
            return favicons.pop()
        elif icons:
            return icons.pop()

    def save(self):
        for rule, favicon_url in self.instances:
            rule.favicon = favicon_url
            rule.save()


class FaviconClient(Client):
    stream = WebsiteStream

    def __init__(self, streams):
        self.streams = streams

    def __enter__(self):
        with ThreadPoolExecutor(max_workers=10) as executor:
            futures = [executor.submit(stream.read) for stream in self.streams]

            for future in as_completed(futures):
                try:
                    payload, stream = future.result()
                except StreamException:
                    continue

                yield payload, stream


class FaviconCollector(Collector):
    feed_client, favicon_client = (FeedClient, FaviconClient)
    url_builder, favicon_builder = (WebsiteURLBuilder, FaviconBuilder)

    def collect(self, rules=[]):
        streams = []

        with self.feed_client(rules=rules) as client:
            for payload, stream in client:
                with self.url_builder(payload, stream) as builder:
                    builder.build()
                    builder.save()

                    if builder.instances:
                        streams.append(WebsiteStream(stream.rule))

        with self.favicon_client(streams) as client:
            for payload, stream in client:
                with self.favicon_builder(payload, stream) as builder:
                    builder.build()
                    builder.save()
