import itertools
from urllib.parse import urlencode

from django.db.models.aggregates import Sum
from django.http.response import HttpResponseRedirect
from django.template.defaultfilters import safe
from django.urls import reverse
from django.utils.functional import cached_property
from django.views.generic.edit import FormView
from django_tables2 import Column
from django_tables2.export.views import ExportMixin
from django_tables2.tables import Table
from django_tables2.views import SingleTableView

from bpp.models import (
    Charakter_Formalny,
    Jednostka,
    OpcjaWyswietlaniaField,
    Rekord,
    Sumy,
    Typ_KBN,
    Uczelnia,
)
from bpp.models.struktura import Wydzial

from .forms import RankingAutorowForm


class RankingAutorowFormularz(FormView):
    form_class = RankingAutorowForm
    nazwa_raportu = "Ranking autorów"

    template_name = "ranking_autorow/form.html"
    success_url = "."

    def get_context_data(self, **kwargs):
        data = FormView.get_context_data(self, **kwargs)
        data["nazwa_raportu"] = self.nazwa_raportu
        return data

    def get_lata(self):
        return (
            Rekord.objects.all()
            .values_list("rok", flat=True)
            .order_by("rok")
            .distinct()
        )

    def get_form_kwargs(self, **kw):
        data = FormView.get_form_kwargs(self, **kw)
        data["lata"] = self.get_lata()
        return data

    def get_raport_arguments(self, form):
        return form.cleaned_data

    def form_valid(self, form):
        url = reverse(
            "bpp:ranking-autorow",
            args=(
                form.cleaned_data["od_roku"],
                form.cleaned_data["do_roku"],
            ),
        )

        params = {}

        # Handle wydzial if present in form (single selection)
        w = form.cleaned_data.get("wydzial")
        if w:
            params["wydzial"] = w.pk

        # Handle jednostka if present in form (single selection)
        j = form.cleaned_data.get("jednostka")
        if j:
            params["jednostka"] = j.pk

        e = form.cleaned_data["_export"]
        if e:
            params["_export"] = e

        params["rozbij_na_jednostki"] = form.cleaned_data["rozbij_na_jednostki"]
        params["tylko_afiliowane"] = form.cleaned_data["tylko_afiliowane"]
        params["bez_nieaktualnych"] = form.cleaned_data["bez_nieaktualnych"]

        # Handle charakter_formalny filter (multiple selection)
        charakter_formalny = form.cleaned_data.get("charakter_formalny")
        if charakter_formalny:
            params["charakter_formalny"] = ",".join(
                [str(c.pk) for c in charakter_formalny]
            )

        # Handle typ_kbn filter (multiple selection)
        typ_kbn = form.cleaned_data.get("typ_kbn")
        if typ_kbn:
            params["typ_kbn"] = ",".join([str(t.pk) for t in typ_kbn])

        return HttpResponseRedirect(url + "?" + urlencode(params))


class RankingAutorowTable(Table):
    class Meta:
        attrs = {"class": "bpp-table"}
        model = Sumy
        order_by = ("-impact_factor_sum", "autor__nazwisko")
        fields = (
            "lp",
            "autor",
            "impact_factor_sum",
            "liczba_cytowan_sum",
            "punkty_kbn_sum",
        )

    lp = Column(
        empty_values=(),
        orderable=False,
        attrs={"td": {"class": "bpp-lp-column"}},
        exclude_from_export=True,
    )

    autor = Column(order_by=("autor__nazwisko", "autor__imiona"))
    punkty_kbn_sum = Column("Punkty MNiSW/MEiN", "punkty_kbn_sum")
    impact_factor_sum = Column("Impact Factor", "impact_factor_sum")
    liczba_cytowan_sum = Column("Liczba cytowań", "liczba_cytowan_sum")

    def render_lp(self):
        self.lp_counter = getattr(
            self, "lp_counter", itertools.count(self.page.start_index())
        )
        return f"{next(self.lp_counter)}."

    def render_autor(self, record):
        url = reverse("bpp:browse_autor", args=(record.autor.slug,))
        return safe(f'<a href="{url}">{record.autor}</a>')

    def value_autor(self, record):
        return str(record.autor)


class RankingAutorowJednostkaWydzialTable(RankingAutorowTable):
    class Meta:
        fields = (
            "lp",
            "autor",
            "jednostka",
            "wydzial",
            "impact_factor_sum",
            "liczba_cytowan_sum",
            "punkty_kbn_sum",
        )
        order_by = ("-impact_factor_sum", "autor__nazwisko")

    jednostka = Column(accessor="jednostka__nazwa")
    wydzial = Column(accessor="jednostka__wydzial__nazwa")


class RankingAutorow(ExportMixin, SingleTableView):
    template_name = "ranking_autorow/results.html"
    export_formats = ["csv", "json", "xls", "xlsx", "ods"]

    def get_export_filename(self, export_format):
        """Generate filename for exports"""
        from datetime import datetime

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        # Build filename parts
        filename_parts = [
            "ranking_autorow",
            str(self.kwargs["od_roku"]),
            str(self.kwargs["do_roku"]),
        ]

        # Add filter abbreviations if any
        charakter_ids = self.get_charakter_formalny_ids()
        if charakter_ids:
            charaktery = Charakter_Formalny.objects.filter(
                pk__in=charakter_ids
            ).values_list("skrot", flat=True)
            if charaktery:
                filename_parts.append("ch_" + "_".join(charaktery))

        typ_ids = self.get_typ_kbn_ids()
        if typ_ids:
            typy = Typ_KBN.objects.filter(pk__in=typ_ids).values_list(
                "skrot", flat=True
            )
            if typy:
                filename_parts.append("typ_" + "_".join(typy))

        filename_parts.append(timestamp)

        return f"{'_'.join(filename_parts)}.{export_format}"

    def get_table_class(self):
        if self.rozbij_na_wydzialy:
            return RankingAutorowJednostkaWydzialTable
        return RankingAutorowTable

    @cached_property
    def rozbij_na_wydzialy(self):
        return self.request.GET.get("rozbij_na_jednostki", "True") == "True"

    @cached_property
    def tylko_afiliowane(self):
        return self.request.GET.get("tylko_afiliowane", "False") == "True"

    @cached_property
    def bez_kol_naukowych(self):
        return getattr(
            Uczelnia.objects.get_for_request(self.request),
            "ranking_autorow_bez_kol_naukowych",
            True,
        )

    @cached_property
    def bez_nieaktualnych(self):
        return self.request.GET.get("bez_nieaktualnych", "True") == "True"

    def _apply_location_filters(self, qset):
        jednostki = self.get_jednostki()
        if jednostki:
            qset = qset.filter(jednostka__in=jednostki)

        uczelnia = Uczelnia.objects.first()
        if uczelnia and uczelnia.uzywaj_wydzialow and not jednostki:
            wydzialy = self.get_wydzialy()
            if wydzialy:
                qset = qset.filter(jednostka__wydzial__in=wydzialy)

        return qset

    def _apply_type_filters(self, qset):
        charakter_formalny_ids = self.get_charakter_formalny_ids()
        if charakter_formalny_ids:
            qset = qset.filter(charakter_formalny_id__in=charakter_formalny_ids)

        typ_kbn_ids = self.get_typ_kbn_ids()
        if typ_kbn_ids:
            qset = qset.filter(typ_kbn_id__in=typ_kbn_ids)

        return qset

    def _apply_exclusions(self, qset):
        qset = qset.exclude(impact_factor_sum=0, liczba_cytowan_sum=0, punkty_kbn_sum=0)
        qset = qset.exclude(autor__pokazuj=False)

        if self.bez_kol_naukowych:
            qset = qset.exclude(
                autor__aktualna_jednostka__rodzaj_jednostki=Jednostka.RODZAJ_JEDNOSTKI.KOLO_NAUKOWE
            )

        if self.bez_nieaktualnych:
            qset = qset.exclude(autor__aktualna_jednostka=None)

        uczelnia = Uczelnia.objects.get_default()
        if uczelnia is not None:
            ukryte_statusy = uczelnia.ukryte_statusy("rankingi")
            if ukryte_statusy:
                qset = qset.exclude(status_korekty_id__in=ukryte_statusy)

        return qset

    def get_queryset(self):
        qset = Sumy.objects.filter(
            rok__gte=self.kwargs["od_roku"], rok__lte=self.kwargs["do_roku"]
        )

        qset = self._apply_location_filters(qset)
        qset = self._apply_type_filters(qset)

        if self.tylko_afiliowane:
            qset = qset.filter(jednostka__skupia_pracownikow=True, afiliuje=True)

        if self.rozbij_na_wydzialy:
            qset = qset.prefetch_related("jednostka__wydzial").select_related(
                "autor", "jednostka"
            )
            qset = qset.group_by("autor", "jednostka")
        else:
            qset = qset.select_related("autor")
            qset = qset.group_by("autor")

        qset = qset.annotate(
            impact_factor_sum=Sum("impact_factor"),
            liczba_cytowan_sum=Sum("liczba_cytowan"),
            punkty_kbn_sum=Sum("punkty_kbn"),
        )

        return self._apply_exclusions(qset)

    def get_dostepne_wydzialy(self):
        return Wydzial.objects.filter(zezwalaj_na_ranking_autorow=True)

    def get_wydzialy(self):
        # Handle single wydzial selection
        wydzial_pk = self.request.GET.get("wydzial", None)
        if wydzial_pk:
            try:
                base_query = self.get_dostepne_wydzialy()
                return base_query.filter(pk=int(wydzial_pk))
            except (TypeError, ValueError):
                pass

        # Return None when no wydzial is explicitly selected
        return None

    def get_dostepne_jednostki(self):
        return Jednostka.objects.filter(widoczna=True, wchodzi_do_raportow=True)

    def get_jednostki(self):
        # Handle single jednostka selection
        jednostka_pk = self.request.GET.get("jednostka", None)
        if jednostka_pk:
            try:
                base_query = self.get_dostepne_jednostki()
                return base_query.filter(pk=int(jednostka_pk))
            except (TypeError, ValueError):
                pass

        # Return None when no jednostka is explicitly selected
        return None

    def get_charakter_formalny_ids(self):
        # Handle multiple charakter_formalny selection
        charakter_formalny_param = self.request.GET.get("charakter_formalny", None)
        if charakter_formalny_param:
            try:
                return [int(pk) for pk in charakter_formalny_param.split(",")]
            except (TypeError, ValueError):
                pass
        return None

    def get_typ_kbn_ids(self):
        # Handle multiple typ_kbn selection
        typ_kbn_param = self.request.GET.get("typ_kbn", None)
        if typ_kbn_param:
            try:
                return [int(pk) for pk in typ_kbn_param.split(",")]
            except (TypeError, ValueError):
                pass
        return None

    def get_context_data(self, **kwargs):
        context = super(SingleTableView, self).get_context_data(**kwargs)
        context["od_roku"] = self.kwargs["od_roku"]
        context["do_roku"] = self.kwargs["do_roku"]
        jeden_rok = False
        if self.kwargs["od_roku"] == self.kwargs["do_roku"]:
            context["rok"] = self.kwargs["od_roku"]
            jeden_rok = True

        # Always handle jednostki
        jednostki = self.get_jednostki()
        context["jednostki"] = jednostki if jednostki else []

        # Build title
        if jeden_rok:
            context["table_title"] = f"Ranking autorów za rok {context['rok']}"
        else:
            context["table_title"] = (
                f"Ranking autorów za lata {context['od_roku']} - {context['do_roku']}"
            )

        # Build subtitle based on filters
        subtitle_parts = []

        # Add jednostki to subtitle if filtered
        if jednostki:
            subtitle_parts.append(", ".join([x.nazwa for x in jednostki]))

        # Check if uczelnia uses wydzialy and handle them
        uczelnia = Uczelnia.objects.first()
        if uczelnia and uczelnia.uzywaj_wydzialow:
            wydzialy = self.get_wydzialy()
            context["wydzialy"] = wydzialy if wydzialy else []

            # Add wydzialy to subtitle if filtered and no jednostki filter
            if not subtitle_parts and wydzialy:
                subtitle_parts.append(", ".join([x.nazwa for x in wydzialy]))
        else:
            context["wydzialy"] = []  # For compatibility

        context["table_subtitle"] = ", ".join(subtitle_parts) if subtitle_parts else ""

        # Add selected filters to context
        charakter_ids = self.get_charakter_formalny_ids()
        if charakter_ids:
            context["selected_charaktery"] = Charakter_Formalny.objects.filter(
                pk__in=charakter_ids
            ).order_by("nazwa")
        else:
            context["selected_charaktery"] = []

        typ_ids = self.get_typ_kbn_ids()
        if typ_ids:
            context["selected_typy"] = Typ_KBN.objects.filter(pk__in=typ_ids).order_by(
                "nazwa"
            )
        else:
            context["selected_typy"] = []

        return context

    def get_table_kwargs(self):
        uczelnia = Uczelnia.objects.all().first()
        pokazuj = uczelnia.pokazuj_liczbe_cytowan_w_rankingu

        if pokazuj == OpcjaWyswietlaniaField.POKAZUJ_NIGDY or (
            pokazuj == OpcjaWyswietlaniaField.POKAZUJ_ZALOGOWANYM
            and self.request.user.is_anonymous
        ):
            return {"exclude": ("liczba_cytowan_sum",)}
        return {}
