import logging
from typing import Dict
from uuid import uuid4

import pendulum
import requests
import trafilatura
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse
from django_extensions.db.models import TimeStampedModel
from htmldate import find_date
from scrobbles.constants import MediaType
from scrobbles.mixins import ScrobblableConstants, ScrobblableItem
from taggit.managers import TaggableManager

logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True}
User = get_user_model()


class Domain(TimeStampedModel):
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    root = models.CharField(max_length=255)
    name = models.CharField(max_length=255, **BNULL)

    tags = TaggableManager(blank=True)

    def __str__(self) -> str:
        if self.name:
            return str(self.name)
        return str(self.root)

    def scrobbles_for_user(self, user_id):
        from scrobbles.models import Scrobble

        return Scrobble.objects.filter(
            web_page__domain=self, user_id=user_id
        ).order_by("-timestamp")


class WebPage(ScrobblableItem):
    MEDIA_TYPE = MediaType.WEBPAGE
    COMPLETION_PERCENT = getattr(settings, "WEBSITE_COMPLETION_PERCENT", 100)

    scrobblableitem_ptr = models.OneToOneField(
        ScrobblableItem,
        parent_link=True,
        primary_key=True,
        on_delete=models.CASCADE,
        related_name="as_webpage",
    )

    url = models.URLField(max_length=500)
    date = models.DateField(**BNULL)
    domain = models.ForeignKey(Domain, on_delete=models.DO_NOTHING, **BNULL)
    extract = models.TextField(**BNULL)

    def __str__(self) -> str:
        if self.title:
            return "{} ({})".format(self.title, self.domain)
        if self.domain:
            return "Unknown ({})".format(self.domain)
        return str(self.uuid)

    def _raw_domain(self):
        self.url.split("//")[-1].split("/")[0]

    def _update_extract_from_web(self, raw_text: str = "", force=True):
        if not raw_text:
            raw_text = requests.get(self.url, headers=headers).text
        if not self.extract or force:
            self.extract = trafilatura.extract(raw_text)
            self.save(update_fields=["extract"])

    def get_absolute_url(self):
        return reverse("webpages:webpage_detail", kwargs={"slug": self.uuid})

    def get_read_url(self):
        return reverse("webpages:webpage_read", kwargs={"slug": self.uuid})

    @property
    def estimated_time_to_read_in_seconds(self):
        if not self.extract:
            return 600

        words_per_minute = getattr(settings, "READING_WORDS_PER_MINUTE", 200)
        words = len(self.extract.split(" "))
        return int(words / words_per_minute) * 60

    @property
    def estimated_time_to_read_in_minutes(self):
        return int(self.estimated_time_to_read_in_seconds / 60)

    @property
    def strings(self) -> ScrobblableConstants:
        return ScrobblableConstants(verb="Browsing", tags="earth_americas")

    @property
    def subtitle(self):
        return self.domain

    @property
    def primary_image_url(self) -> str:
        # TODO Figure out how to add a preview?
        return ""

    def scrobble_for_user(self, user_id, *args, **kwargs):
        scrobble = super().scrobble_for_user(user_id)
        scrobble.push_to_archivebox()
        return scrobble

    def scrobbles(self, user):
        Scrobble = apps.get_model("scrobbles", "Scrobble")
        return Scrobble.objects.filter(user=user, web_page=self).order_by(
            "-timestamp"
        )

    def clean_title(self, title: str, save=True):
        if len(title.split("|")) > 1:
            title = title.split("|")[0]
        if len(title.split("&#8211;")) > 1:
            title = title.split("&#8211;")[0]
        if len(title.split(" - ")) > 1:
            title = title.split(" - ")[0]
        self.title = title.strip()

        if save:
            self.save(update_fields=["title"])

    def _update_domain_from_url(self, save=False):
        domain = self.url.split("//")[-1].split("/")[0].split("www.")[-1]
        self.domain, created = Domain.objects.get_or_create(root=domain)

        if save:
            self.save(update_fields=["domain"])

    def _update_title_from_web(self, raw_text: str, save=False):
        if not raw_text:
            return

        self.title = raw_text[
            raw_text.find("<title>") + 7 : raw_text.find("</title>")
        ]

        if not self.title and self.extract:
            first_line = self.extract.split("\n")[0]
            self.title = first_line[:254]

        if save:
            self.save(update_fields=["title"])

    def _update_date_from_web(self, save=False):
        try:
            date_str = find_date(str(self.url))
        except ValueError:
            date_str = ""
        if date_str:
            self.date = pendulum.parse(date_str).date()

        if save:
            self.save(update_fields=["date"])

    def push_to_archivebox(self, url: str, username: str, password: str):
        login_url = requests.compat.urljoin(url, "admin/login/")
        session = requests.Session()
        response = session.get(login_url)
        csrf_token = response.cookies.get_dict().get("csrftoken")
        response = session.post(
            login_url,
            data={
                "username": username,
                "password": password,
                "csrfmiddlewaretoken": csrf_token,
            },
        )
        try:
            response = session.post(
                requests.compat.urljoin(url, "add/"),
                data={
                    "url": self.url + "\n",
                    "tags": "vrobbler",
                    "depth": "0",
                    "parser": "auto",
                },
                timeout=2,
            )
        except requests.exceptions.ReadTimeout:
            return

        if response.status_code == 200:
            logger.info(
                "Website already exists in archive", extra={"url": self.url}
            )
        else:
            raise Exception(
                f"Failed to push URL to archivebox (Response {response.status_code})"
            )

    def fetch_data_from_web(self, save=True, force=True):
        raw_text = trafilatura.fetch_url(self.url)
        if not self.extract or force:
            self.extract = trafilatura.extract(
                raw_text,
                include_links=False,
                include_comments=False,
            )

        if not self.title or force:
            self._update_title_from_web(raw_text)

        if not self.date or force:
            self._update_date_from_web()

        if not self.domain or force:
            self._update_domain_from_url()

        if not self.base_run_time_seconds or force:
            self.base_run_time_seconds = self.estimated_time_to_read_in_seconds

        if save:
            self.save()

    @classmethod
    def find_or_create(cls, data_dict: Dict) -> "WebPage":
        """Given a data dict from an manual URL scrobble, does the heavy lifting of looking up
        the url, creating if if doesn't exist yet.

        """
        # TODO Add constants for all these data keys
        if "url" not in data_dict.keys():
            logger.error("No url in data dict")
            return

        webpage = cls.objects.filter(url=data_dict.get("url")).first()

        if not webpage:
            webpage = cls(url=data_dict.get("url"))
            webpage.fetch_data_from_web(save=True)
        return webpage
