from datetime import date

from django.contrib.messages import info
from django.contrib.postgres.aggregates import StringAgg
from django.db.models import F
from django.shortcuts import get_object_or_404
from django.utils.dateparse import parse_date
from django.utils.functional import cached_property
from rest_framework.exceptions import AuthenticationFailed
from wbcore import viewsets
from wbcore.viewsets.mixins import OrderableMixin
from wbcrm.models import Account
from wbportfolio.models import Product

from wbcommission.models import Commission, CommissionRule, CommissionType

from ..filters import CommissionAccountFilterSet, CommissionRuleCommissionFilterSet
from ..models.account_service import AccountRebateManager
from ..serializers import (
    CommissionModelSerializer,
    CommissionTypeModelSerializer,
    CommissionTypeRepresentationSerializer,
)
from ..serializers.commissions import CommissionRepresentationSerializer, CommissionRuleCommissionModelSerializer
from .buttons import CommissionButtonConfig
from .display import CommissionDisplayConfig, CommissionRuleCommissionDisplayConfig, CommissionTypeDisplayConfigClass
from .endpoints import CommissionEndpointConfig, CommissionRuleCommissionEndpointConfig
from .mixins import CommissionPermissionMixin
from .titles import CommissionTitleConfig


class CommissionTypeRepresentationModelViewSet(viewsets.RepresentationViewSet):
    serializer_class = CommissionTypeRepresentationSerializer
    queryset = CommissionType.objects.all()
    ordering_fields = search_fields = ordering = ["name"]


class CommissionTypeModelViewSet(viewsets.ModelViewSet):
    serializer_class = CommissionTypeModelSerializer
    queryset = CommissionType.objects.all()
    ordering_fields = search_fields = ordering = ["name"]
    display_config_class = CommissionTypeDisplayConfigClass


class CommissionRepresentationModelViewSet(viewsets.RepresentationViewSet):
    serializer_class = CommissionRepresentationSerializer
    queryset = Commission.objects.all()
    ordering_fields = search_fields = ordering = ["id"]


class CommissionModelViewSet(OrderableMixin, CommissionPermissionMixin, viewsets.ModelViewSet):
    display_config_class = CommissionDisplayConfig
    title_config_class = CommissionTitleConfig
    endpoint_config_class = CommissionEndpointConfig
    button_config_class = CommissionButtonConfig

    serializer_class = CommissionModelSerializer

    filterset_class = CommissionAccountFilterSet
    queryset = Commission.objects.select_related(
        "account",
        "crm_recipient",
        "account_role_type_recipient",
        "commission_type",
    )
    ordering = ["order"]

    @cached_property
    def account(self) -> Account:
        return get_object_or_404(Account, pk=self.kwargs["account_id"])

    @cached_property
    def commission_type(self) -> CommissionType:
        return get_object_or_404(CommissionType, pk=self.kwargs["commission_type_id"])

    @cached_property
    def simulation_date(self) -> date | None:
        if compute_date_str := self.request.GET.get("only_valid_at_date"):
            return parse_date(compute_date_str)

    @cached_property
    def simulation_product(self) -> Product | None:
        if product_id := self.request.GET.get("simulate_for_product"):
            return Product.objects.get(id=product_id)

    @cached_property
    def actual_percents(self) -> dict | None:
        if self.simulation_product and self.simulation_date:
            rebate_manager = AccountRebateManager(
                self.account.get_root(), self.commission_type.key
            )  # This will be loaded dynamically in a future iteration
            rebate_manager.initialize()
            idx = rebate_manager.df_fees.index.get_level_values(1)
            latest_valid_date = idx[idx <= self.simulation_date].max()
            if latest_valid_date != self.simulation_date:
                info(
                    self.request,
                    f"To simulate the recipient percent, We had to move the date to the latest valid date ({self.simulation_date:%Y-%m-%d} -> {latest_valid_date:%Y-%m-%d})",
                )

            terminal_accounts_total_holding = rebate_manager.get_terminal_accounts_total_holding(latest_valid_date)
            terminal_account_holding_ratio = rebate_manager.get_terminal_account_holding_ratio(
                self.account,
                self.simulation_product,
                latest_valid_date,
            )
            res = {}
            if terminal_account_holding_ratio:
                res = dict(
                    self.commission_type.get_valid_commissions(
                        self.account, latest_valid_date, self.simulation_product, terminal_accounts_total_holding
                    )
                )
            return res

    def get_queryset(self):
        return (
            super()
            .get_queryset()
            .filter(commission_type=self.commission_type, account__in=self.account.get_ancestors(include_self=True))
            .annotate(
                _group_key=F("id"),
                roles_repr=StringAgg(F("roles__person__computed_str"), delimiter=", "),
            )
        )


class CommissionRuleCommissionModelViewSet(viewsets.ModelViewSet):
    display_config_class = CommissionRuleCommissionDisplayConfig
    endpoint_config_class = CommissionRuleCommissionEndpointConfig

    serializer_class = CommissionRuleCommissionModelSerializer
    filterset_class = CommissionRuleCommissionFilterSet
    queryset = CommissionRule.objects.select_related("commission")
    ordering = ["percent", "id"]

    @cached_property
    def commission(self) -> Commission:
        commission_id = self.kwargs.get("commission_id", self.request.GET.get("commission"))
        if Commission.objects.filter(id=commission_id).filter_for_user(self.request.user).exists():
            return get_object_or_404(Commission, pk=commission_id)
        raise AuthenticationFailed()

    def get_queryset(self):
        return (
            super()
            .get_queryset()
            .filter(commission=self.commission)
            .annotate(net_commission=F("commission__net_commission"))
        )
