import datetime
from functools import cached_property

from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib import messages
from django.core.paginator import Paginator
from django.db import models
from django.forms.models import model_to_dict
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import render
from django.utils import timezone
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import Tag, TaggedItemBase
from wagtail.admin.panels import (
    FieldPanel,
    HelpPanel,
    MultiFieldPanel,
    ObjectList,
    PageChooserPanel,
    TabbedInterface,
)
from wagtail.blocks import PageChooserBlock, RichTextBlock
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from wagtail.search import index
from wagtailmetadata.models import MetadataPageMixin

from calendar_utils.models import CalendarMixin
from shared.blocks import (
    ArticleDownloadBlock,
    ArticleQuoteBlock,
    MainMenuItemBlock,
    NavbarMenuItemBlock,
    OtherLinksBlock,
    PersonContactBlock,
    PersonContactBoxBlock,
    SocialLinkBlock,
    TwoTextColumnBlock,
)
from shared.const import MONTH_NAMES
from shared.forms import SubscribeForm
from shared.utils import make_promote_panels, subscribe_to_newsletter
from tuning import admin_help

from .base import (  # MenuMixin,
    ArticleMixin,
    ArticlesMixin,
    ArticlesPageMixin,
    ExtendedMetadataHomePageMixin,
    ExtendedMetadataPageMixin,
)
from .base import MenuMixin as MenuMixinBase
from .base import SharedTaggedMainArticle, SubpageMixin  # MenuMixin,


class MainMenuMixin(MenuMixinBase):
    important_item_name = models.CharField(
        verbose_name="Jméno",
        help_text="Pokud není odkazovaná stránka na Majáku, použij možnost zadání samotné adresy níže.",
        max_length=16,
        blank=True,
        null=True,
    )

    important_item_page = models.ForeignKey(
        Page,
        verbose_name="Stránka",
        null=True,
        blank=True,
        related_name="+",
        on_delete=models.PROTECT,
    )

    important_item_url = models.URLField(
        verbose_name="Adresa",
        blank=True,
        null=True,
    )

    menu = StreamField(
        [("menu_item", MainMenuItemBlock())],  # , ("menu_parent", MenuParentBlock())
        verbose_name="Položky",
        blank=True,
        use_json_field=True,
    )

    menu_panels = [
        MultiFieldPanel(
            [
                FieldPanel("important_item_name"),
                FieldPanel("important_item_page"),
                FieldPanel("important_item_url"),
            ],
            heading="Důležitá položka menu",
        ),
        MultiFieldPanel(
            [
                FieldPanel("menu"),
            ],
            heading="Další obsah menu",
        ),
    ]

    class Meta:
        abstract = True


class PageInMenuMixin(Page):
    def get_menu_title(self) -> str:
        for menu_item in self.root_page.menu:
            if menu_item.value["page"] is None:
                continue

            if menu_item.value["page"].id == self.id:
                return menu_item.value["title"]

        return self.title

    class Meta:
        abstract = True


class MainHomePageMixin(
    MainMenuMixin,
    RoutablePageMixin,
    ExtendedMetadataHomePageMixin,
    MetadataPageMixin,
    ArticlesMixin,
    Page,
):
    # header

    menu_button_name = models.CharField(
        verbose_name="Text na tlačítku pro zapojení", max_length=16
    )

    menu_button_content = StreamField(
        [
            ("navbar_menu_item", NavbarMenuItemBlock()),
        ],
        verbose_name="Obsah menu pro zapojení se",
        blank=True,
        use_json_field=True,
    )

    # content
    # NOTE: Needs to be overriden
    content = StreamField(
        [],
        verbose_name="Hlavní obsah",
        blank=True,
        use_json_field=True,
    )

    # footer
    footer_other_links = StreamField(
        [
            ("other_links", OtherLinksBlock()),
        ],
        verbose_name="Odkazy v zápatí webu",
        blank=True,
        use_json_field=True,
    )

    footer_person_list = StreamField(
        [("person", PersonContactBlock())],
        verbose_name="Osoby v zápatí webu",
        blank=True,
        max_num=6,
        use_json_field=True,
    )

    # settings
    @property
    def gdpr_and_cookies_page(self):
        # NOTE: Must be implemented
        raise NotImplementedError

    matomo_id = models.IntegerField(
        "Matomo ID pro sledování návštěvnosti", blank=True, null=True
    )

    social_links = StreamField(
        [
            ("social_links", SocialLinkBlock()),
        ],
        verbose_name="Odkazy na sociální sítě",
        blank=True,
        use_json_field=True,
    )

    content_panels = Page.content_panels + [
        FieldPanel("content"),
        FieldPanel("footer_other_links"),
        FieldPanel("footer_person_list"),
    ]

    promote_panels = make_promote_panels(admin_help.build(admin_help.IMPORTANT_TITLE))

    settings_panels = [
        FieldPanel("menu_button_name"),
        FieldPanel("menu_button_content"),
        PageChooserPanel("gdpr_and_cookies_page"),
        FieldPanel("social_links"),
        FieldPanel("matomo_id"),
    ]

    ### EDIT HANDLERS

    edit_handler = TabbedInterface(
        [
            ObjectList(content_panels, heading="Obsah"),
            ObjectList(promote_panels, heading="Propagovat"),
            ObjectList(settings_panels, heading="Nastavení"),
            ObjectList(MainMenuMixin.menu_panels, heading="Menu"),
        ]
    )

    ### RELATIONS

    subpage_types = []

    ### OTHERS

    class Meta:
        verbose_name = "Hlavní stránka"
        abstract = True

    @property
    def article_page_model(self):
        # NOTE: Must be overridden
        raise NotImplementedError

    @property
    def articles_page_model(self):
        # NOTE: Must be overridden
        raise NotImplementedError

    @property
    def contact_page_model(self):
        # NOTE: Must be overridden
        raise NotImplementedError

    @property
    def search_page_model(self):
        # NOTE: Must be overridden
        raise NotImplementedError

    @cached_property
    def gdpr_and_cookies_url(self):
        if self.gdpr_and_cookies_page:
            return self.gdpr_and_cookies_page.url

        return "#"

    @staticmethod
    def get_404_response(request):
        return render(request, "styleguide2/404.html", status=404)

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, args, kwargs)

        context["article_data_list"] = self.materialize_shared_articles_query(
            self.append_all_shared_articles_query(
                self.article_page_model.objects.live().all()
            )
            .order_by("-union_date")[:3]
        )

        return context

    def get_region_response(self, request):
        context = {}
        if request.GET.get("region", None) == "VSK":
            sorted_article_qs = self.article_page_model.objects.filter(
                region__isnull=False
            ).order_by("-date")

            context = {"article_data_list": sorted_article_qs[:3]}
        else:
            sorted_article_qs = self.article_page_model.objects.filter(
                region=request.GET.get("region", None)
            )[:3]

            context = {"article_data_list": sorted_article_qs[:3]}

        data = {
            "html": render(
                request, "styleguide2/includes/small_article_preview.html", context
            ).content.decode("utf-8")
        }

        return JsonResponse(data=data, safe=False)

    def serve(self, request, *args, **kwargs):
        if request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
            if "region" in request.GET:
                return self.get_region_response(request)

        return super().serve(request, *args, **kwargs)

    @cached_property
    def newsletter_subscribe_url(self):
        newsletter_subscribe = self.reverse_subpage("newsletter_subscribe")
        return (
            self.url + newsletter_subscribe
            if self.url is not None
            else newsletter_subscribe
        )  # preview fix

    @property
    def articles_page(self):
        return self._first_subpage_of_type(self.articles_page_model)

    @property
    def people_page(self):
        return self._first_subpage_of_type(MainPeoplePage)

    @property
    def contact_page(self):
        return self._first_subpage_of_type(self.contact_page_model)

    @property
    def search_page(self):
        return self._first_subpage_of_type(self.search_page_model)

    @property
    def root_page(self):
        return self

    @route(r"^prihlaseni-k-newsletteru/$")
    def newsletter_subscribe(self, request):
        if request.method == "POST":
            form = SubscribeForm(request.POST)
            if form.is_valid():
                subscribe_to_newsletter(
                    form.cleaned_data["email"], settings.PIRATICZ_NEWSLETTER_CID
                )

                messages.success(
                    request,
                    "Zkontroluj si prosím schránku, poslali jsme ti potvrzovací email.",
                )

                try:
                    page = (
                        Page.objects.filter(id=form.cleaned_data["return_page_id"])
                        .live()
                        .first()
                    )
                    return HttpResponseRedirect(page.full_url)
                except Page.DoesNotExist:
                    return HttpResponseRedirect(self.url)

            messages.error(
                request,
                "Tvůj prohlížeč nám odeslal špatná data. Prosím, zkus to znovu.",
            )

        return HttpResponseRedirect(self.url)

    @route(r"^feeds/atom/$")
    def view_feed(self, request):
        from shared.feeds import LatestArticlesFeed  # noqa

        return LatestArticlesFeed()(request, self.articles_page.id)

    def _first_subpage_of_type(self, page_type) -> Page or None:
        try:
            return self.get_descendants().type(page_type).live().specific()[0]
        except IndexError:
            return None

    @route(r"^sdilene/$", name="shared")
    def shared(self, request):
        return self.setup_article_page_context(request)


class MainArticlesPageMixin(
    RoutablePageMixin,
    ExtendedMetadataPageMixin,
    SubpageMixin,
    MetadataPageMixin,
    ArticlesPageMixin,
    PageInMenuMixin,
    Page,
):
    last_import_log = models.TextField(
        "Výstup z posledního importu", null=True, blank=True
    )
    perex = models.TextField()

    import_panels = [
        MultiFieldPanel(
            [
                FieldPanel("do_import"),
                FieldPanel("collection"),
                FieldPanel("dry_run"),
                FieldPanel("jekyll_repo_url"),
                FieldPanel("readonly_log"),
                HelpPanel(
                    "Import provádějte vždy až po vytvoření stránky aktualit. "
                    'Pro uložení logu je nutné volit možnost "Publikovat", nikoliv'
                    'pouze "Uložit koncept". '
                    "Import proběhne na pozadí a může trvat až několik minut. "
                    "Dejte si po spuštění importu kávu a potom obnovte stránku pro "
                    "zobrazení výsledku importu."
                ),
            ],
            "import z Jekyll repozitáře",
        ),
    ]

    ### RELATIONS

    parent_page_types = []  # NOTE: Must be implemented
    subpage_types = []  # NOTE: Must be implemented

    ### PANELS
    content_panels = Page.content_panels + [
        FieldPanel("perex"),
        FieldPanel("shared_tags"),
    ]
    promote_panels = make_promote_panels()

    ### EDIT HANDLERS

    edit_handler = TabbedInterface(
        [
            ObjectList(content_panels, heading="Obsah"),
            ObjectList(promote_panels, heading="Propagovat"),
            ObjectList(import_panels, heading="Import"),
        ]
    )

    ### OTHERS

    class Meta:
        verbose_name = "Rozcestník článků"
        abstract = True

    def get_base_shared_articles_query(self, filter: models.Q):
        return self.materialize_shared_articles_query(
            self.append_all_shared_articles_query(
                self.root_page.article_page_model.objects.filter(filter).live().all()
            )
            .order_by("-union_date")
        )

    def get_article_data_list(
        self,
        months_back: int = 1,
        search_filter: models.Q | None = None,
    ):
        if search_filter is None:
            search_filter = models.Q()

        target_date_list = (
            self.append_all_shared_articles_query(
                self.root_page.article_page_model.objects.filter(search_filter).live()
            )
            .order_by("-union_date")
        )

        if not target_date_list:
            return []

        target_date = target_date_list[0]["union_date"] - relativedelta(months=months_back)
        first_day_of_target_month = target_date.replace(day=1)

        filter = models.Q(date__gte=first_day_of_target_month) & search_filter

        sorted_article_qs = self.get_base_shared_articles_query(filter)

        article_data_list = []

        current_month_data = self.get_empty_month_data(timezone.now().date())

        for article in sorted_article_qs:
            if article.date.month != current_month_data["month_number"]:
                if len(current_month_data["articles"]) != 0:
                    # append completed month if it contains any articles
                    article_data_list.append(current_month_data)

                current_month_data = self.get_empty_month_data(article.date)

            current_month_data["articles"].append(article)

        article_data_list.append(current_month_data)  # last iteration

        return article_data_list

    def get_search_filters(self, request):
        filter = models.Q()

        if "tag_id" in request.GET:
            tag = self.get_filtered_tag(request)

            if tag is not None:
                filter = filter & models.Q(tags__id=tag.id)

        if "q" in request.GET:
            filter = filter & models.Q(title__icontains=self.get_search_query(request))

        return filter

    def get_filtered_tag(self, request) -> Tag | None:
        if "tag_id" in request.GET:
            try:
                return Tag.objects.filter(id=int(request.GET["tag_id"])).first()
            except Exception:
                pass

        return None

    def get_search_query(self, request) -> str | None:
        if "q" in request.GET:
            return request.GET["q"]

    def get_context(self, request, get_articles: bool = True, *args, **kwargs):
        ctx = super().get_context(request, args, kwargs)

        if get_articles:
            filtered_tag = self.get_filtered_tag(request)

            if filtered_tag is not None:
                ctx["filtered_tag"] = filtered_tag

            search_query = self.get_search_query(request)

            if search_query is not None:
                ctx["search_query"] = search_query

            search_filter = self.get_search_filters(request)

            article_timeline_list = self.get_article_data_list(1, search_filter)

            ctx["article_timeline_list"] = article_timeline_list
            ctx["show_next_timeline_articles"] = len(
                self.get_base_shared_articles_query(search_filter)
            ) != 0 and (
                self.materialize_shared_articles_query(
                    self.append_all_shared_articles_query(
                        self.root_page.article_page_model.objects.filter(search_filter).live().all()
                    )
                    .order_by("union_date")[:2]  # LIMIT 2
                )[0]
                not in article_timeline_list[-1]["articles"]
            )

            tags = []

            for article in self.root_page.article_page_model.objects.all()[:50]:
                for tag in article.tags.all():
                    if tag in tags:
                        continue

                    tags.append(tag)

            ctx["tags"] = tags

            # meow

        return ctx

    def get_timeline_articles_response(self, request):
        try:
            months = int(request.GET.get("months", None))
        except ValueError:
            months = 1

        search_filter = self.get_search_filters(request)
        article_timeline_list = self.get_article_data_list(months, search_filter)

        context = {"article_timeline_list": article_timeline_list}

        data = {
            "html": render(
                request,
                "styleguide2/includes/organisms/articles/articles_timeline_list.html",
                context,
            ).content.decode("utf-8"),
            "has_next": (
                len(self.get_base_shared_articles_query(search_filter)) != 0
                and (
                    self.materialize_shared_articles_query(
                        self.append_all_shared_articles_query(
                            self.root_page.article_page_model.objects.filter(
                                search_filter
                            ).live().all()
                        )
                        .order_by("union_date")[:2]  # LIMIT 2
                    )[0]
                    not in article_timeline_list[-1]["articles"]
                )
            ),
        }

        return JsonResponse(data=data, safe=False)

    @route(r"^sdilene/$", name="shared")
    def shared(self, request):
        return self.setup_article_page_context(request)

    def serve(self, request, *args, **kwargs):
        if request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
            if "months" in request.GET:
                return self.get_timeline_articles_response(request)

        return super().serve(request, *args, **kwargs)

    @staticmethod
    def get_empty_month_data(date_obj):
        return {
            "month_number": date_obj.month,
            "month_text": MONTH_NAMES[date_obj.month - 1],
            "articles": [],
        }


class MainArticlePageMixin(
    ArticleMixin,
    ExtendedMetadataPageMixin,
    SubpageMixin,
    MetadataPageMixin,
    PageInMenuMixin,
    Page,
):
    ### FIELDS
    content = StreamField(
        [
            (
                "text",
                RichTextBlock(
                    template="styleguide2/includes/atoms/text/prose_richtext.html"
                ),
            ),
            ("quote", ArticleQuoteBlock()),
            ("download", ArticleDownloadBlock()),
        ],
        verbose_name="Článek",
        blank=True,
        use_json_field=True,
    )

    @property
    def tags(self):
        # NOTE: Must be implemented
        raise NotImplementedError

    @property
    def shared_tags(self):
        # NOTE: Must be implemented
        raise NotImplementedError

    search_fields = ArticleMixin.search_fields + [
        index.SearchField("author_page"),
        index.FilterField("slug"),
    ]

    ### PANELS

    content_panels = ArticleMixin.content_panels + [
        FieldPanel("author_page"),
        FieldPanel("tags"),
        FieldPanel("shared_tags"),
    ]

    promote_panels = make_promote_panels(
        admin_help.build(admin_help.NO_SEO_TITLE, admin_help.NO_DESCRIPTION_USE_PEREX),
        search_image=False,
    )

    ### RELATIONS

    parent_page_types = []  # NOTE: Must be implemented
    subpage_types = []  # NOTE: Must be implemented

    ### OTHERS

    class Meta:
        verbose_name = "Aktualita"
        abstract = True


class MainContactPageMixin(
    ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PageInMenuMixin, Page
):
    ### FIELDS

    contact_people = StreamField(
        [("item", PersonContactBlock())],
        verbose_name="Kontaktní osoby",
        blank=True,
        use_json_field=True,
    )
    contact_boxes = StreamField(
        [("item", PersonContactBoxBlock())],
        verbose_name="Kontaktní boxy",
        blank=True,
        use_json_field=True,
    )
    text = StreamField(
        [("two_columns_text", TwoTextColumnBlock())],
        verbose_name="Kontaktní informace",
        blank=True,
        use_json_field=True,
    )

    ### PANELS

    content_panels = Page.content_panels + [
        FieldPanel("text"),
        FieldPanel("contact_people"),
        FieldPanel("contact_boxes"),
    ]

    promote_panels = make_promote_panels()

    settings_panels = []

    ### RELATIONS

    parent_page_types = []  # NOTE: Must be implemented
    subpage_types = []  # NOTE: Must be implemented

    ### OTHERS

    class Meta:
        verbose_name = "Kontakty"
        abstract = True


class MainSearchPageMixin(
    ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PageInMenuMixin, Page
):
    parent_page_types = []  # NOTE: Must be implemented
    subpage_types = []  # NOTE: Must be implemented

    class Meta:
        verbose_name = "Vyhledávací stránka"
        abstract = True

    @property
    def searchable_models(self) -> list:
        # NOTE: Must be implemented
        return []

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, args, kwargs)

        context["results"] = []

        if request.GET.get("q", "") == "":
            return context

        search_query = request.GET["q"]
        context["global_search_query"] = search_query

        for model in self.searchable_models:
            filter = models.Q(title__icontains=search_query)

            if hasattr(model, "perex"):
                filter = filter | models.Q(perex__icontains=search_query)

            results = model.objects.filter(filter)

            if hasattr(model, "date"):
                results = results.order_by("-date")

            context["results"] += list(results.all()[:15])

        context["results"].sort(
            # Put results without a date first, as they'll be person litsings etc.
            key=lambda result: result.date
            if hasattr(result, "date")
            else datetime.date(year=9999, month=1, day=1),
            reverse=True,
        )

        return context


class MainSimplePageMixin(
    ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PageInMenuMixin, Page
):
    ### FIELDS

    # content
    content = StreamField(
        [
            (
                "text",
                RichTextBlock(
                    template="styleguide2/includes/atoms/text/prose_richtext.html"
                ),
            ),
        ],
        verbose_name="Hlavní obsah",
        blank=True,
        use_json_field=True,
    )

    ### PANELS

    content_panels = Page.content_panels + [FieldPanel("content")]

    promote_panels = make_promote_panels()

    settings_panels = []

    ### RELATIONS

    parent_page_types = []  # NOTE: Must be implemented
    subpage_types = []  # NOTE: Must be implemented

    ### OTHERS
    class Meta:
        verbose_name = "Jednoduchá stárnka"
        abstract = True