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,
    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,

# MenuMixin,


class MainFooterMixin(Page):
    footer_other_links = StreamField(
        [
            ("other_links", OtherLinksBlock()),
        ],
        verbose_name="Odkazy v zápatí webu",
        blank=True,
        use_json_field=True,
    )

    class Meta:
        abstract = True


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("menu"),
            ],
            heading="Obsah menu",
        ),
        MultiFieldPanel(
            [
                FieldPanel("important_item_name"),
                FieldPanel("important_item_page"),
                FieldPanel("important_item_url"),
            ],
            heading="Blikající položka menu na začátku",
        ),
    ]

    class Meta:
        abstract = True


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

    menu_panels = [FieldPanel("social_links")]

    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,
    SocialMixin,
    RoutablePageMixin,
    ExtendedMetadataHomePageMixin,
    MetadataPageMixin,
    ArticlesMixin,
    MainFooterMixin,
    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
    # NOTE: Needs to be oberriden
    footer_person_list = StreamField(
        [],
        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
    )

    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))

    menu_panels = (
        MainMenuMixin.menu_panels
        + SocialMixin.menu_panels
        + [
            FieldPanel("menu_button_name"),
            FieldPanel("menu_button_content"),
        ]
    )

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

    ### EDIT HANDLERS

    edit_handler = TabbedInterface(
        [
            ObjectList(content_panels, heading="Obsah"),
            ObjectList(promote_panels, heading="Propagovat"),
            ObjectList(settings_panels, heading="Nastavení"),
            ObjectList(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_timestamp")[: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("-timestamp")

            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(self.articles_page_model, self.article_page_model)(
            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

    ARTICLE_LIST_COUNT = 20

    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_timestamp")
        )

    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)

            articles = self.get_base_shared_articles_query(search_filter)[
                : self.ARTICLE_LIST_COUNT + 1
            ]

            more_articles_exist = len(articles) > self.ARTICLE_LIST_COUNT

            articles = articles[: self.ARTICLE_LIST_COUNT]

            ctx["articles"] = articles
            ctx["show_next_timeline_articles"] = more_articles_exist
            ctx["article_count"] = len(articles)

            tags = []
            tag_count = {}

            for article in self.root_page.article_page_model.objects.child_of(
                self
            ).all()[:150]:
                for tag in article.tags.all():
                    if tag not in tags:
                        tag_count[tag] = 1

                    if tag in tags:
                        tag_count[tag] += 1

                        continue

                    tags.append(tag)

            tags.sort(key=lambda tag: tag_count[tag], reverse=True)

            # Limit to a maximum of 30 tags
            tags = tags[:30]

            ctx["tags"] = tags

            # meow

        return ctx

    def get_timeline_articles_response(self, request):
        article_count = request.GET.get("article_count", "")

        search_filter = self.get_search_filters(request)

        if article_count.isnumeric():
            article_count = int(article_count)
        else:
            article_count = self.ARTICLE_LIST_COUNT

        articles = self.get_base_shared_articles_query(search_filter)[
            : article_count + 1
        ]

        more_articles_exist = len(articles) > article_count

        articles = articles[:article_count]

        context = {"articles": articles}

        data = {
            "html": render(
                request,
                "styleguide2/includes/organisms/articles/articles_timeline_list.html",
                context,
            ).content.decode("utf-8"),
            "has_next": more_articles_exist,
        }

        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 "article_count" in request.GET:
                return self.get_timeline_articles_response(request)

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


class MainArticlePageMixin(
    ArticleMixin,
    ExtendedMetadataPageMixin,
    SubpageMixin,
    MetadataPageMixin,
    PageInMenuMixin,
    Page,
):
    ### FIELDS
    show_initial_image = models.BooleanField(
        verbose_name="Ukázat obrázek v textu",
        help_text="Pokud je tato volba zaškrtnutá, obrázek nastavený u tohoto článku se automaticky vloží do prvního odstavce.",
        default=True,
    )

    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("show_initial_image"),
        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(
        [],
        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, "timestamp"):
                results = results.order_by("-timestamp")

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

        context["results"].sort(
            # Put results without a timestamp first, as they'll be person litsings etc.
            key=lambda result: result.timestamp
            if hasattr(result, "timestamp")
            else datetime.datetime(year=9999, month=1, day=1).replace(
                tzinfo=datetime.timezone.utc
            ),
            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


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

    perex_col_1 = models.TextField(
        verbose_name="Perex - první sloupec",
    )
    perex_col_2 = models.TextField(
        verbose_name="Perex - druhý sloupec",
    )

    ### PANELS

    content_panels = Page.content_panels + [
        FieldPanel("perex_col_1"),
        FieldPanel("perex_col_2"),
        FieldPanel("people"),
    ]

    promote_panels = make_promote_panels()

    settings_panels = []

    ### RELATIONS

    parent_page_types = ["main.MainHomePage"]
    subpage_types = [
        "main.MainPersonPage",
        "main.MainSimplePage",
    ]

    ### OTHERS

    @property
    def perex(self) -> str:
        return self.perex_col_1 + " \n" + self.perex_col_2

    class Meta:
        verbose_name = "Lidé a týmy"
        abstract = True


class MainPersonPageMixin(
    ExtendedMetadataPageMixin,
    SubpageMixin,
    MetadataPageMixin,
    CalendarMixin,
    PageInMenuMixin,
    Page,
):
    ### FIELDS
    main_image = models.ForeignKey(
        "wagtailimages.Image",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
        verbose_name="Hlavní obrázek",
        related_name="+",
    )

    profile_image = models.ForeignKey(
        "wagtailimages.Image",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
        verbose_name="Profilový obrázek",
        related_name="+",
    )
    before_name = models.CharField(
        "Tituly před jménem", max_length=32, blank=True, null=True
    )
    after_name = models.CharField(
        "Tituly za jménem", max_length=16, blank=True, null=True
    )
    position = models.CharField(
        "Funkce", max_length=200, blank=True, null=True, help_text="Např. 'Předseda'"
    )
    primary_group = models.CharField(
        "Kategorie",
        help_text="např. 'Europarlament' nebo 'Sněmovna'",
        max_length=64,
        blank=True,
        null=True,
    )
    perex = models.TextField()
    text = RichTextField()

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

    related_people = StreamField(
        [
            (
                "person",
                PageChooserBlock(page_type="main.MainPersonPage", label="Detail osoby"),
            )
        ],
        verbose_name="Další lidé",
        blank=True,
        use_json_field=True,
    )

    email = models.CharField("E-mail", max_length=128, blank=True, null=True)
    phone = models.CharField("Telefonní kontakt", max_length=16, blank=True, null=True)

    settings_panels = []

    ### RELATIONS

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

    ### PANELS
    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel("main_image"),
                FieldPanel("profile_image"),
            ],
            heading="Obrázky",
        ),
        MultiFieldPanel(
            [FieldPanel("before_name"), FieldPanel("after_name")],
            heading="Titul",
        ),
        MultiFieldPanel(
            [FieldPanel("position"), FieldPanel("perex"), FieldPanel("text")],
            heading="Informace",
        ),
        MultiFieldPanel(
            [
                FieldPanel("email"),
                FieldPanel("phone"),
                FieldPanel("social_links"),
            ],
            heading="Kontakt",
        ),
        FieldPanel("calendar_url"),
        FieldPanel("related_people"),
    ]

    def get_context(self, request) -> dict:
        context = super().get_context(request)

        context["article_page_list"] = (
            self.root_page.article_page_model.objects.filter(author_page=self.id)
            .order_by("-timestamp")
            .live()[:3]
        )

        return context

    ### OTHERS

    class Meta:
        verbose_name = "Detail osoby"
        abstract = True
        # ordering = ("title",)