from typing import Generator

from wbcompliance.models.risk_management import backend
from wbcompliance.models.risk_management.dispatch import register
from wbcore import serializers as wb_serializers
from wbfdm.models import Instrument, InstrumentList
from wbfdm.serializers.instruments.instrument_lists import (
    InstrumentListRepresentationSerializer,
)

from wbportfolio.pms.typing import Portfolio as PortfolioDTO

from .mixins import ActivePortfolioRelationshipMixin


@register("Instrument List Portfolio Rule Backend", rule_group_key="portfolio")
class RuleBackend(ActivePortfolioRelationshipMixin):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instrument_list_type:
            self.instrument_lists = InstrumentList.objects.filter(instrument_list_type=self.instrument_list_type)

        self.instrument_lists_repr = " ,".join(map(lambda x: x.name, self.instrument_lists))
        self.severity = self.thresholds[0].severity

    @classmethod
    def get_serializer_class(cls) -> wb_serializers.Serializer:
        class RuleBackendSerializer(wb_serializers.Serializer):
            exclude = wb_serializers.BooleanField(
                default=True,
                label="Exclude",
                help_text="If true, the rule will check that the portfolio composition DOES NOT intersect the given instrument list",
            )
            instrument_list_type = wb_serializers.ChoiceField(
                choices=InstrumentList.InstrumentListType.choices,
                required=False,
                default=None,
                allow_null=True,
                help_text="If specified, will dynamically load the list of instrument list to check of the same specified type",
                label="Instrument List Type",
            )
            instrument_lists = wb_serializers.PrimaryKeyRelatedField(
                queryset=InstrumentList.objects.all(),
                many=True,
                default=None,
                allow_null=True,
                label="Instrument Lists",
            )
            _instrument_lists = InstrumentListRepresentationSerializer(many=True)

            @classmethod
            def get_parameter_fields(cls):
                return [
                    "exclude",
                    "instrument_list_type",
                    "instrument_lists",
                ]

        return RuleBackendSerializer

    def _get_breached_instruments(self, instruments: list[Instrument]) -> list[InstrumentList]:
        breached_instruments = []
        for instrument_list in self.instrument_lists:
            if (
                instrument_list.get_instruments(self.evaluation_date, only_validated=True)
                .filter(id__in=instruments.values("id"))
                .exists()
            ):
                breached_instruments.append(instrument_list)
        return breached_instruments

    def _process_dto(self, portfolio: PortfolioDTO, **kwargs) -> Generator[backend.IncidentResult, None, None]:
        for instrument_id in portfolio.positions_map.keys():
            instrument = Instrument.objects.get(id=instrument_id)
            ancestors = instrument.get_ancestors(include_self=True)
            breached_instruments = self._get_breached_instruments(ancestors)

            if self.exclude and len(breached_instruments) > 0:
                report_details = {
                    "Instrument Lists": ", ".join(map(lambda o: o.name, breached_instruments)),
                }
                yield backend.IncidentResult(
                    breached_object=instrument,
                    breached_object_repr=str(instrument),
                    breached_value=f"# {len(breached_instruments)}",
                    report_details=report_details,
                    severity=self.severity,
                )
            elif not self.exclude and len(breached_instruments) == 0:
                report_details = {"Instrument Lists": self.instrument_lists_repr}
                yield backend.IncidentResult(
                    breached_object=instrument,
                    breached_object_repr=str(instrument),
                    breached_value="# 0",
                    report_details=report_details,
                    severity=self.severity,
                )
