from django.db import models
from django.db.models.options import override
from django.utils.translation import gettext as _
from django.core.files import File

from django.conf import settings

import logging
logger = logging.getLogger(__name__)

import requests
import favicon
import random

class Keyword(models.Model):
    """
    this is the model that should contain searchable keywords
    """
    text_de = models.CharField(max_length=40)
    text_en = models.CharField(max_length=40)

    def __str__(self):
        return f"{{<{self.__class__.__name__}>\"{self.text_en}\"}}"

    class Meta:
        verbose_name = _("Keyword")
        verbose_name_plural = _("keywords")

class Searchable(models.Model):
    """
    Abstract class for any model that should be searchable.
    This model will be searched for by the searchbox that is in every upper part of the website.

    This class is not a real abstract class, I need to query it in the main search, so thats impossible
    """
    title_de = models.CharField(max_length=50, default="Titel DE")
    title_en = models.CharField(max_length=50, default="title EN")
    subtitle_de = models.CharField(max_length=50, blank=True)
    subtitle_en = models.CharField(max_length=50, blank=True)
    desc_de = models.TextField(max_length=250, unique=False, default="Beschreibung DE")
    desc_en = models.TextField(max_length=250, unique=False, default="Description EN")
    # may be empty/blank for some entries
    date = models.DateField(blank=True, null=True)
    keywords = models.ManyToManyField(Keyword)
    suburl = models.CharField(max_length=200, blank=True, null=True)
    public = models.BooleanField(default=True)

    @classmethod
    def regenerate_all_entries(cls):
        """
        regenerate all searchable items
        """
        logger.info(f"regenerating all {Searchable.__name__} entries")
        for obj in cls.objects.all():
            obj.regenerate()

    def __str__(self):
        return f"{{<{self.__class__.__name__}>\"{self.title_en}\"}}"

    def regenerate(self):
        """
        regenerate a object
        """
        raise NotImplementedError(f"{self.__class__.__name__} does not implement regenerate")

    class Meta:
        verbose_name = _("Searchable")
        verbose_name_plural = _("Searchables")

class StaticSite(Searchable):
    """
    This model represents any static site, such as start:index, 
    that should show up in search.

    Every searchable view should inherit from start.views.SearchableView.
    # TODO automate scanning for SearchableView classes
    """
    
    def regenerate(self):
        """
        regenerate a object
        """
        logger.info(f"regenerating {self.__class__.__name__} object: {self}")
        logger.warning(f"{self.__class__.__name__} cannot regenerate.")
        #self.save()
        pass

    class Meta:
        verbose_name = _("static site")
        verbose_name_plural = _("static sites")

class Link(Searchable):
    """
    contains all my interesting links
    """

    # the actual link
    url = models.URLField(unique=True, null=False, primary_key=True)
    favicon_dir: str = "img/links/favicons"
    favicon = models.ImageField(blank=True, upload_to=favicon_dir, null=True)
    status = models.BooleanField(default=False)
    personal = models.BooleanField(default=False)

    def __str__(self):
        return f"{{<{self.__class__.__name__}>\"{self.title_en}\"}}"

    def regenerate(self):
        """
        regenerate a object

        Implements the abstract method of Searchable

        Searches for a favicon in the urr, if one is found, it will be stored in MEDIA_ROOT

        TODO filepathings here suck and are too error prone
        """
        logger.info(f"regenerating {self.__class__.__name__} object: {self}")
        self.suburl = f"/links#{self.title_en}"
        logger.info(f"getting favicon for link object {self}: '{self.url}'")
        try:
            icons = favicon.get(self.url, timeout=2)
        except (ConnectionError) as ce:
            # just keep whatever was stored if we cant get a new favicon
            logger.warn(f"unable to download favicon for {self}: {ce.with_traceback(None)}")
            self.status = False

        except Exception as e:
            logger.warn(f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
            self.status = False

        else:
            response = requests.get(icons[0].url, stream=True)
            filename: str = f"favicon-{random.randint(0x1000, 0xffff)}.{icons[0].format}"
            try:
                with open(f"/tmp/{filename}", 'wb') as image:
                    for chunk in response.iter_content(1024):
                        image.write(chunk)
                    logger.debug(image)
                with open(f"/tmp/{filename}", 'rb') as image:
                    self.favicon.save(filename, File(image))
                logger.info(f"downloaded favicon for {self}")
                self.status = True

            except FileNotFoundError as fe:
                logger.error(f"cant write favicon to file for {self}: {fe}")
                self.favicon = None
            
            except Exception as e:
                logger.warn(f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
                self.favicon = None

        self.save()

    class Meta:
        verbose_name = _("Link")
        verbose_name_plural = _("Links")
