from datetime import datetime
from bs4 import BeautifulSoup
from urllib.parse import urlparse
import time as t
from django.utils.translation import gettext_lazy as _


from ..extractor import *
import json

import logging

logger = logging.getLogger(__name__)

class SimpleFacebookEvent:
    def __init__(self, data):
        self.elements = {}

        for key in ["id", "start_timestamp", "end_timestamp"]:
            self.elements[key] = data[key] if key in data else None

        if "parent_event" in data:
            self.parent = SimpleFacebookEvent(
                data["parent_event"]
            )

class FacebookEvent:
    name = "event"
    # keys corresponds to javascript elements that are containing interesting data
    keys = [
        [
            "start_time_formatted",
            "start_timestamp",
            "is_past",
            "name",
            "price_info",
            "cover_media_renderer",
            "id",
            "parent_if_exists_or_self",
            "day_time_sentence",
            "event_place",
            "comet_neighboring_siblings",
        ],
        ["event_description"],
        ["start_timestamp", "end_timestamp"],
    ]
    # rules are defined by a sub-key within intersesting data where elements will be find
    # each pair in the associated list is a key of our model and a path within FB data to
    # get the corresponding field
    rules = {
        "event_description":  [("description", ["text"])],
        "cover_media_renderer": [
            ("image_alt", ["cover_photo", "photo", "accessibility_caption"]),
            ("image", ["cover_photo", "photo", "full_image", "uri"]),
            ("image", ["cover_media", 0, "full_image", "uri"]),
            ("image_alt", ["cover_media", 0, "accessibility_caption"]),
         ],
        "event_creator":
            [("event_creator_name", ["name"]),
             ("event_creator_url", ["url"]),
            ],
        "event_place": [("event_place_name", ["name"])],
    }

    def __init__(self, *args):
        if len(args) == 1:
            other = args[0]
            self.fragments = other.fragments
            self.elements = other.elements
            self.neighbor_events = None
        else:
            i = args[0]
            event = args[1]
            self.fragments = {}
            self.elements = {}
            self.neighbor_events = None
            self.possible_end_timestamp = []
            self.add_fragment(i, event)

    def get_element(self, key):
        return self.elements[key] if key in self.elements else None

    def get_element_date(self, key):
        v = self.get_element(key)
        return (
            datetime.fromtimestamp(v).date() if v is not None and v != 0 else None
        )

    def get_element_time(self, key):
        v = self.get_element(key)
        return (
            datetime.fromtimestamp(v).strftime("%H:%M")
            if v is not None and v != 0
            else None
        )

    def add_fragment(self, i, event):
        self.fragments[i] = event

        if FacebookEvent.keys[i] == [
            "start_timestamp",
            "end_timestamp",
        ]:
            self.get_possible_end_timestamp(i, event)
        else:
            for k in FacebookEvent.keys[i]:
                if k == "comet_neighboring_siblings":
                    self.get_neighbor_events(event[k])
                elif k in FacebookEvent.rules:
                    for nk, rule in FacebookEvent.rules[k]:
                        error = False
                        c = event[k]
                        for ki in rule:
                            if c is not None and ki in c or (isinstance(c, list) and ki < len(c)):
                                c = c[ki]
                            else:
                                error = True
                                break
                        if not error:
                            self.elements[nk] = c
                else:
                    self.elements[k] = event[k]

    def get_possible_end_timestamp(self, i, data):
        self.possible_end_timestamp.append(
            dict((k, data[k]) for k in FacebookEvent.keys[i])
        )

    def get_neighbor_events(self, data):
        self.neighbor_events = [
            SimpleFacebookEvent(d) for d in data
        ]

    def __str__(self):
        return (
            str(self.elements)
            + "\n Neighbors: "
            + ", ".join([ne.elements["id"] for ne in self.neighbor_events])
        )

    def consolidate_current_event(self):
        if (
            self.neighbor_events is not None
            and "id" in self.elements
            and "end_timestamp" not in self.elements
        ):
            if self.neighbor_events is not None and "id" in self.elements:
                id = self.elements["id"]
                for ne in self.neighbor_events:
                    if ne.elements["id"] == id:
                        self.elements["end_timestamp"] = ne.elements[
                            "end_timestamp"
                        ]

        if (
            "end_timestamp" not in self.elements
            and len(self.possible_end_timestamp) != 0
        ):
            for s in self.possible_end_timestamp:
                if (
                    "start_timestamp" in s
                    and "start_timestamp" in self.elements
                    and s["start_timestamp"] == self.elements["start_timestamp"]
                ):
                    self.elements["end_timestamp"] = s["end_timestamp"]
                    break

    def find_event_fragment_in_array(array, event, first=True):
        if isinstance(array, dict):
            seen = False
            for i, ks in enumerate(FacebookEvent.keys):
                # DEBUG: print([k for k in ks if k in array], "il manque", [k for k in ks if k not in array])
                if len(ks) == len([k for k in ks if k in array]):
                    seen = True
                    if event is None:
                        event = FacebookEvent(i, array)
                    else:
                        event.add_fragment(i, array)
                    # only consider the first of FacebookEvent.keys
                    break
            if not seen:
                for k in array:
                    event = FacebookEvent.find_event_fragment_in_array(
                        array[k], event, False
                    )
        elif isinstance(array, list):
            for e in array:
                event = FacebookEvent.find_event_fragment_in_array(
                    e, event, False
                )

        if event is not None and first:
            event.consolidate_current_event()
        return event

    def build_event(self, url):
        return {
            "title": self.get_element("name"),
            "category": None,
            "start_day": self.get_element_date("start_timestamp"),
            "location": self.get_element("event_place_name"),
            "description": self.get_element("description"),
            "tags": [],
            "uuids": [url],
            "url_human": url,
            "start_time": self.get_element_time("start_timestamp"),
            "end_day": self.get_element_date("end_timestamp"),
            "end_time": self.get_element_time("end_timestamp"),
            "image": self.get_element("image"),
            "image_alt": self.get_element("image_alt"),
        }

    def get_parent_id(self):
        return self.get_element("parent_if_exists_or_self")["id"]


    def build_events(self, url):

        if self.neighbor_events is None or len(self.neighbor_events) == 0:
            return [self.build_event(url)]
        else:
            url_base = "https://www.facebook.com/events/" + self.get_parent_id() + "/"
            result = []
            for nb_e in self.neighbor_events:
                # we create a copy of the event
                clone = FacebookEvent(self)
                # we set start and end timestamp accordnig to the neighbor
                clone.elements["start_timestamp"] = nb_e.elements["start_timestamp"]
                clone.elements["end_timestamp"] = nb_e.elements["end_timestamp"]
                ## we generate the event
                result.append(clone.build_event(url_base + nb_e.elements["id"] + "/"))
            return result

class CExtractor(Extractor):

    def __init__(self):
        super().__init__()
        self.has_2nd_method = True

    def prepare_2nd_extract_dler(downloader): 
        if downloader.support_2nd_extract:
            from selenium.webdriver.common.by import By
            from selenium.webdriver.support.ui import WebDriverWait
            from selenium.webdriver.support import expected_conditions as EC

            path = './/div[not(@aria-hidden)]/div[@aria-label="Allow all cookies"]'
            try:
                element = WebDriverWait(downloader.driver, 15).until(EC.visibility_of_element_located((By.XPATH, path)))
            except Exception as e:
                raise Exception(_("Error while waiting for the cookie button to be visible: " + e.__class__.__name__ + ' ' + str(e)))
            try:
                button = downloader.driver.find_element(By.XPATH, path)
            except Exception as e:
                raise Exception(_("Error while getting the cookie button to be visible: " + e.__class__.__name__ + ' ' + str(e)))
            try:
                button.click()
            except Exception as e:
                raise Exception(_("Error while clicking on the cookie button to be visible: " + e.__class__.__name__ + ' ' + str(e)))
            t.sleep(5)

    def prepare_2nd_extract(self):
        CExtractor.prepare_2nd_extract_dler(self.downloader)


    def clean_url(url):
        if CExtractor.is_known_url(url, False):
            u = urlparse(url)
            result = "https://www.facebook.com" + u.path

            # remove name in the url
            match = re.match(r"(.*/events)/s/([a-zA-Z-][a-zA-Z-0-9-]+)/([0-9/]*)", result)
            if match:
                result = match[1] + "/" + match[3]

            if result[-1] == "/":
                return result
            else:
                return result + "/"
        else:
            return url


    def is_known_url(url, include_links=True):
        u = urlparse(url)
        url_list = ["facebook.com", "www.facebook.com", "m.facebook.com"]
        if include_links:
            url_list.append("fb.me")
        return u.netloc in url_list

    def extract(
        self, content, url, url_human=None, default_values=None, published=False
    ):
        # NOTE: this method does not use url_human = None and default_values = None

        # get step by step all information from the content
        fevent = None
        soup = BeautifulSoup(content, "html.parser")
        for json_script in soup.find_all("script", type="application/json"):
            json_txt = json_script.get_text()
            json_struct = json.loads(json_txt)
            fevent = FacebookEvent.find_event_fragment_in_array(
                json_struct, fevent
            )

        if fevent is not None:
            self.set_header(url)
            for event in fevent.build_events(url):
                logger.warning("published: " + str(published))
                event["published"] = published

                if default_values and "category" in default_values:
                    event["category"] = default_values["category"]
                self.add_event(default_values, **event)
            return self.get_structure()
        else:
            logger.warning("cannot find any event in page")
            raise Exception(
                    _("Cannot get Facebook event from {}").format(url)
                )

        return None
