from contextlib import nullcontext
from DateTime import DateTime
from itertools import islice
from logging import getLogger
from plone import api
from plone.app.textfield.value import RichTextValue
from plone.app.vocabularies.catalog import CatalogSource
from plone.autoform import directives
from plone.autoform.interfaces import IFormFieldProvider
from plone.dexterity.interfaces import IDexterityContent
from plone.namedfile.file import NamedBlobImage
from plone.supermodel import model
from ploneintranet.core import _
from ploneintranet.news.content import INewsSection
from ploneintranet.news.interfaces import IBannerWithHero
from ploneintranet.news.interfaces import IBannerWithIcon
from ploneintranet.news.interfaces import IMagazineHome
from ploneintranet.news.interfaces import ISplashPage
from ploneintranet.news.subscribers.modified import fix_news_description
from urllib.parse import urlparse
from z3c.relationfield.schema import RelationChoice
from z3c.relationfield.schema import RelationList
from zope import schema
from zope.component import adapter
from zope.interface import alsoProvides
from zope.interface import implementer
from zope.interface import noLongerProvides
from zope.interface import provider
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary

import feedparser
import os
import requests

logger = getLogger(__name__)


@provider(IFormFieldProvider)
class INewsMeta(model.Schema):
    """Extra newsitem metadata for magazine"""

    article_image = schema.Bool(
        title=_(
            "label_article_image",
            default="Image visible as hero image on the news article page.",
        ),
        required=False,
        default=True,
    )

    hero_image_aspect_ratio = schema.Choice(
        title=_("label_hero_image_aspect_ratio", default="Hero image aspect ratio"),
        source="ploneintranet.news.vocabulary.hero_image_aspect_ratio",
        required=False,
        default=None,
    )

    magazine_image = schema.Bool(
        title=_(
            "label_magazine_image", default="Image visible on news overview pages."
        ),
        required=False,
        default=True,
    )

    magazine_home = schema.Bool(
        title=_("label_magazine_home", default="Visible on news landing page"),
        required=False,
        default=False,
    )

    # commenting not implemented yet
    allow_comments = schema.Bool(
        title=_("label_allow_comments", default="Allow comments"),
        required=False,
        default=True,
    )

    read_more_link = schema.TextLine(title="Read more link", required=False, default="")

    translations = RelationList(
        title=_("Translations"),
        value_type=RelationChoice(
            title=_("Translation"),
            source=CatalogSource(portal_type="News Item"),
        ),
        defaultFactory=list,
        required=False,
    )

    carousel_show_as = schema.Choice(
        title=_("Show carousel as"),
        source="ploneintranet.news.vocabulary.carousel_show_as",
        required=False,
        default="carousel",
    )

    carousel_height = schema.Choice(
        title=_("Carousel height"),
        source="ploneintranet.news.vocabulary.carousel_height",
        required=False,
        default="adaptive",
    )

    carousel_position = schema.Choice(
        title=_("Carousel position"),
        source="ploneintranet.news.vocabulary.carousel_position",
        required=False,
        default="above",
    )

    carousel_slides = RelationList(
        title=_("Slide images"),
        value_type=RelationChoice(
            source=CatalogSource(portal_type="Image"),
        ),
        defaultFactory=list,
        required=False,
    )


@implementer(INewsMeta)
@adapter(IDexterityContent)
class NewsMeta:
    schema = INewsMeta

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

    def __getattr__(self, name):
        if name not in self.schema:
            return super().__getattr__(name)
        elif name == "magazine_home":
            return IMagazineHome.providedBy(self.context)
        return getattr(self.context, name)

    def __setattr__(self, name, value):
        if name not in self.schema:
            super().__setattr__(name, value)
        elif name == "magazine_home":
            if value is True:
                alsoProvides(self.context, IMagazineHome)
            else:
                noLongerProvides(self.context, IMagazineHome)
        else:
            setattr(self.context, name, value)


@provider(IFormFieldProvider)
class IBannerBehavior(model.Schema):
    """Make an object a banner"""

    banner = schema.ASCII(
        title=_("label_banner", default="Banner"),
        description=_(
            "help_banner",
            default="Add the name of a banner interface or leave it empty.",
        ),
        required=False,
        default="",
    )

    banner_icon = schema.ASCII(
        title=_("label_banner_icon", default="Banner icon"),
        required=False,
        default="lightbulb",
    )


@implementer(IBannerBehavior)
@adapter(IBannerBehavior)
class BannerBehavior:
    """This adapter allows z3c.form to mark an object as a Banner

    Instead of storing the value in an attribute,
    it applies or removes a marker interface
    """

    banner_ifaces = (IBannerWithHero, IBannerWithIcon)

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

    @property
    def banner_icon(self):
        return self.context.banner_icon

    @banner_icon.setter
    def banner_icon(self, value):
        self.context.banner_icon = value

    def iface_getter(self, ifaces):
        for iface in ifaces:
            if iface.providedBy(self.context):
                return iface.__identifier__
        return ""

    def iface_setter(self, value, ifaces):
        for iface in ifaces:
            if value == iface.__identifier__:
                alsoProvides(self.context, iface)
            else:
                noLongerProvides(self.context, iface)

    @property
    def banner(self):
        for iface in self.banner_ifaces:
            if iface.providedBy(self.context):
                return iface.__identifier__
        return ""

    @banner.setter
    def banner(self, value):
        for iface in self.banner_ifaces:
            if value == iface.__identifier__:
                alsoProvides(self.context, iface)
            else:
                noLongerProvides(self.context, iface)


@provider(IFormFieldProvider)
class ISplashPageBehavior(model.Schema):
    """Make an object a splash page"""

    is_splash_page = schema.Bool(
        title=_("label_is_splash_page", default="Is a splash page"),
        required=False,
        default=False,
    )


@implementer(ISplashPageBehavior)
@adapter(ISplashPageBehavior)
class SplashPageBehavior:
    """This adapter allows z3c.form to mark an object as a Splash page

    Instead of storing the value in an attribute,
    it applies or removes a marker interface
    """

    splash_iface = ISplashPage

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

    @property
    def is_splash_page(self):
        return self.splash_iface.providedBy(self.context)

    @is_splash_page.setter
    def is_splash_page(self, value):
        if value:
            alsoProvides(self.context, self.splash_iface)
        else:
            noLongerProvides(self.context, self.splash_iface)


@provider(IFormFieldProvider)
class INewsApp(model.Schema):
    """Storage for the selected news sections and layout"""

    grid = schema.Choice(
        title=_("grid", default="Grid"),
        vocabulary=SimpleVocabulary(
            [
                SimpleTerm(value="sl", title=_("Small Left Column")),
                SimpleTerm(value="sr", title=_("Small Right Column")),
                SimpleTerm(value="sc", title=_("Single Column")),
                SimpleTerm(value="tc", title=_("Three Columns")),
            ]
        ),
        default="sc",
    )

    columns = schema.Dict(
        title="Configuration for the magazine columns", defaultFactory=dict
    )

    # Those fields will be edited in the panel-edit-frontpage form
    directives.omitted("grid", "columns")


@provider(IFormFieldProvider)
class INewsSyndication(model.Schema):
    """Storage for the RSS/Atom feed"""

    feed_url = schema.TextLine(title="RSS/Atom feed")

    news_from_feed_creator = schema.TextLine(
        title=_("The user that will create the the news when the feed is consumed"),
        required=False,
    )


@implementer(INewsSyndication)
@adapter(INewsSection)
class NewsSyndication:
    def __init__(self, context):
        self.context = context

    def get_image_url(self, entry):
        for i in entry.get("links", []):
            if i["rel"] == "enclosure":
                url = i["href"]
                if url:
                    return url
        for i in entry.get("content", []):
            if i["type"].startswith("image/"):
                url = i["src"]
                if url:
                    return url

    def entry2news_item(self, entry):
        """Take a feed parser entry and try hard to make a news item out of it"""
        news_item = api.content.create(
            container=self.context,
            type="News Item",
            title=entry.title,
            text=RichTextValue(getattr(entry, "description", "")),
            read_more_link=getattr(entry, "link", ""),
        )
        INewsMeta(news_item).magazine_home = True
        fix_news_description(news_item)
        image_url = self.get_image_url(entry)
        if image_url:
            path = urlparse(image_url).path
            img_name = os.path.basename(path)
            img_request = requests.get(image_url)
            img_data = img_request.content
            news_item.image = NamedBlobImage(data=img_data, filename=img_name)
        api.content.transition(news_item, to_state="published")

        # Check if we can set an effective date from the feed entry
        # if not we set it to now
        parsed_effective_date = getattr(entry, "published", None)
        if not parsed_effective_date:
            parsed_effective_date = getattr(entry, "updated", None)
        if parsed_effective_date:
            news_item.setEffectiveDate(DateTime(parsed_effective_date))
        else:
            news_item.setEffectiveDate(DateTime())

        news_item.reindexObject()

    def sync_news(self):
        context = self.context
        feed = feedparser.parse(context.feed_url)
        items = context.listFolderContents()
        titles = {i.title for i in items}
        stop = api.portal.get_registry_record(
            "ploneintranet.news.feed_stop", default=-1
        )
        entries = (entry for entry in feed.entries if entry.title not in titles)
        entries = sorted(
            entries,
            key=lambda entry: DateTime(getattr(entry, "published", None)),
            reverse=True,
        )
        stop = stop if stop > 0 else None

        news_from_feed_creator = context.news_from_feed_creator
        if news_from_feed_creator:
            maybe_adopt_user = api.env.adopt_user(username=news_from_feed_creator)
        else:
            maybe_adopt_user = nullcontext()

        with maybe_adopt_user:
            for entry in islice(entries, None, stop):
                try:
                    self.entry2news_item(entry)
                except Exception:
                    logger.exception(
                        "Cannot create news item from feed element %r", entry
                    )


@provider(IFormFieldProvider)
class INewsSmartSection(model.Schema):
    """Storage for the smart section attributes"""

    view_name = schema.TextLine(title="Smart View Name")
    calendar = schema.TextLine(title="Calendar")


@implementer(INewsSmartSection)
@adapter(INewsSection)
class NewsSmartSection:
    def __init__(self, context):
        self.context = context
