import math

from django.conf import settings
from django.contrib.sitemaps import Sitemap
from django.db.models import Exists, OuterRef, Q
from django.urls import reverse

from wagtail.contrib.sitemaps import sitemap_generator
from wagtail.models import Locale, Page

from oscar.core.loading import get_model

from ocyan.core.fender import config
from ocyan.main.loading import get_ocyan_class

from .constants import ALTERNATES, INCLUDE_CHILD_PRODUCTS_IN_SITEMAP, PAGE_SIZE

get_offline_request = get_ocyan_class(
    "wagtail_utils.utils",
    "get_offline_request",
)

Product = get_model("catalogue", "Product")
ProductCategory = get_model("catalogue", "ProductCategory")
Category = get_model("catalogue", "Category")


class FastI18nSitemap(Sitemap):
    i18n = config.i18n_enabled
    alternates = config.i18n_enabled and ALTERNATES
    limit = PAGE_SIZE

    def __init__(self):
        super().__init__()
        if self.i18n:
            # divide the limit per page by the number of languages.
            # we are going to print each url in each language.
            _num_languages = len(self._languages())
            self.limit = math.floor(self.__class__.limit / _num_languages)

    def _items(self):
        return self.items()

    def _urls(self, page, protocol, domain):
        if not self.i18n:
            return super()._urls(page, protocol, domain)
        else:
            urls = []
            latest_lastmod = None
            all_items_lastmod = True  # track if all items have a lastmod

            paginator_page = self.paginator.page(page)
            for _item in paginator_page.object_list:
                for lang in self._languages():
                    # print each url in each language
                    item = (_item, lang)
                    loc = f"{protocol}://{domain}{self._location(item)}"
                    priority = self._get("priority", item)
                    lastmod = self._get("lastmod", item)

                    if all_items_lastmod:
                        all_items_lastmod = lastmod is not None
                        if all_items_lastmod and (
                            latest_lastmod is None or lastmod > latest_lastmod
                        ):
                            latest_lastmod = lastmod

                    url_info = {
                        "item": item,
                        "location": loc,
                        "lastmod": lastmod,
                        "changefreq": self._get("changefreq", item),
                        "priority": str(priority if priority is not None else ""),
                        "alternates": [],
                    }

                    if self.alternates:
                        for lang_code in self._languages():
                            loc = f"{protocol}://{domain}{self._location(item, lang_code)}"
                            url_info["alternates"].append(
                                {
                                    "location": loc,
                                    "lang_code": lang_code,
                                }
                            )
                        if self.x_default:
                            lang_code = settings.LANGUAGE_CODE
                            loc = f"{protocol}://{domain}{self._location(item, lang_code)}"
                            loc = loc.replace(f"/{lang_code}/", "/", 1)
                            url_info["alternates"].append(
                                {
                                    "location": loc,
                                    "lang_code": "x-default",
                                }
                            )

                    urls.append(url_info)

            if all_items_lastmod and latest_lastmod:
                # pylint: disable=attribute-defined-outside-init
                self.latest_lastmod = latest_lastmod

            return urls


class WagtailSitemap(sitemap_generator.Sitemap):
    def items(self):
        # Start with an empty QuerySet
        combined_queryset = Page.objects.none()

        # Iterate through all locales
        for locale in Locale.objects.all():
            translated_root_page = (
                self.get_wagtail_site().root_page.get_translation_or_none(locale)
            )

            if translated_root_page is not None:
                locale_pages = (
                    translated_root_page.get_descendants(inclusive=True)
                    .live()
                    .public()
                    .defer_streamfields()
                    .specific()
                    .order_by("path")
                )
                combined_queryset = combined_queryset.union(locale_pages)

        return combined_queryset

    def _urls(self, page, protocol, domain):
        self.request = get_offline_request(domain)
        return super()._urls(page, protocol, domain)


class ShopSitemap(FastI18nSitemap):
    def items(self):
        return ["offer:list"]

    # pylint: disable=arguments-renamed
    def location(self, item):
        return reverse(item)


class ProductSitemap(FastI18nSitemap):
    def lastmod(self, item):
        return item.date_updated

    def items(self):
        objects = Product._default_manager  # pylint: disable=protected-access
        if INCLUDE_CHILD_PRODUCTS_IN_SITEMAP:
            return objects.filter(
                Q(
                    Exists(ProductCategory.objects.filter(product_id=OuterRef("pk"))),
                    structure__in=[Product.PARENT, Product.STANDALONE],
                )
                | Q(
                    Exists(
                        ProductCategory.objects.filter(product_id=OuterRef("parent_id"))
                    ),
                    structure=Product.CHILD,
                ),
                is_public=True,
            ).only("id", "slug", "date_updated")
        else:
            return (
                objects.filter(
                    is_public=True,
                    structure__in=[Product.PARENT, Product.STANDALONE],
                )
                .exclude(productcategory__isnull=True)
                .only("id", "slug", "date_updated")
            )


class CategorySitemap(FastI18nSitemap):
    def items(self):
        return Category.objects.filter(is_public=True).only("id", "slug", "code")


def gather_sitemaps():
    return {
        "pages": WagtailSitemap,
        "shop": ShopSitemap,
        "products": ProductSitemap,
        "categories": CategorySitemap,
    }
