from __future__ import annotations
from onegov.election_day.models.election_compound.mixins import \
    DerivedAttributesMixin
from onegov.election_day.models.party_result.mixins import \
    HistoricalPartyResultsMixin
from onegov.election_day.models.party_result.mixins import \
    PartyResultsCheckMixin
from typing import Any, Generic, TypeVar, TYPE_CHECKING
if TYPE_CHECKING:
    import datetime
    from onegov.election_day.models import Election
    from onegov.election_day.models import ElectionCompound
    from onegov.election_day.models import ElectionCompoundRelationship
    from onegov.election_day.models import PartyResult
    from onegov.election_day.types import DomainOfInfluence  # noqa: F401
    from onegov.core.orm import SessionManager  # noqa: F401
    from sqlalchemy.orm import Query
[docs]
class inherited_attribute(Generic[T]):  # noqa: N801
[docs]
    def __set_name__(
        self,
        owner: type[Any],
        name: str
    ) -> None:
        self.name = name 
[docs]
    def __get__(
        self,
        # NOTE: Because we mix ElectionCompoundPart with other objects mypy
        #       will complain about inherited_attribute not working on these
        #       other objects, since information is lost during type union
        #       so for now we ignore type safety here, it should be fine
        instance: Any,
        owner: type[Any]
    ) -> T:
        return getattr(instance.election_compound, self.name) 
 
[docs]
class ElectionCompoundPart(
    DerivedAttributesMixin, PartyResultsCheckMixin, HistoricalPartyResultsMixin
):
    """ A part of an election compound.
    Covers a part of an election compound between the domain of the compound
    and the domain of the elections.
    There is no database object behind a part of an election compound, all
    the results are either taken from the compound (parties) or elections
    (candidates)-
    """
    def __init__(
        self,
        election_compound: ElectionCompound,
        domain: str,
        segment: str
    ):
[docs]
        self.election_compound = election_compound 
[docs]
        self.election_compound_id = (
            election_compound.id if election_compound else None
        ) 
[docs]
        self.domain = domain 
[docs]
        self.id = segment.replace(' ', '-').lower() 
[docs]
    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, ElectionCompoundPart)
            and self.election_compound_id == other.election_compound_id
            and self.domain == other.domain
            and self.segment == other.segment
        ) 
    # NOTE: These top three attributes are a bit ugly due to the mixins
    #       forward declaring these attributes as columns, even though they
    #       can be arbitrary readable attributes, which cannot be expressed
    #       yet using a Protocol, once we can do that, this should become
    #       cleaner
[docs]
    date: inherited_attribute[datetime.date] = (
        inherited_attribute['datetime.date']()) 
[docs]
    completes_manually: inherited_attribute[bool] = (
        inherited_attribute[bool]()) 
[docs]
    manually_completed: inherited_attribute[bool] = (
        inherited_attribute[bool]()) 
[docs]
    pukelsheim = inherited_attribute[bool]() 
[docs]
    last_result_change = inherited_attribute['datetime.datetime | None']() 
[docs]
    last_change = inherited_attribute['datetime.datetime | None']() 
[docs]
    last_modified = inherited_attribute['datetime.datetime | None']() 
[docs]
    domain_elections = inherited_attribute['DomainOfInfluence']() 
[docs]
    colors = inherited_attribute[dict[str, str]]() 
[docs]
    voters_counts = inherited_attribute[bool]() 
[docs]
    exact_voters_counts = inherited_attribute[bool]() 
[docs]
    horizontal_party_strengths = inherited_attribute[bool]() 
[docs]
    show_party_strengths = inherited_attribute[bool]() 
[docs]
    use_historical_party_results = inherited_attribute[bool]() 
[docs]
    session_manager = inherited_attribute['SessionManager']() 
    @property
[docs]
    def title(self) -> str:
        return f'{self.election_compound.title} {self.segment}' 
    @property
[docs]
    def short_title(self) -> str | None:
        if not self.election_compound.short_title:
            return None
        return f'{self.election_compound.short_title} {self.segment}' 
    @property
[docs]
    def title_translations(self) -> dict[str, str]:
        return {
            locale: f'{title} {self.segment}'
            for locale, title
            in self.election_compound.title_translations.items()
        } 
    @property
[docs]
    def short_title_translations(self) -> dict[str, str]:
        translations = self.election_compound.short_title_translations or {}
        return {
            locale: f'{title} {self.segment}'
            for locale, title
            in translations.items()
        } 
    @property
[docs]
    def elections(self) -> list[Election]:
        return [
            election for election in
            self.election_compound.elections
            if election.domain_supersegment == self.segment
        ] 
    @property
[docs]
    def progress(self) -> tuple[int, int]:
        result = [e.completed for e in self.elections]
        return sum(1 for r in result if r), len(result) 
    @property
[docs]
    def party_results(self) -> list[PartyResult]:  # type:ignore[override]
        return [
            result for result in self.election_compound.party_results
            if (
                result.domain == self.domain
                and result.domain_segment == self.segment
            )
        ] 
    @property
[docs]
    def has_results(self) -> bool:
        """ Returns True, if the election compound has any results. """
        if self.has_party_results:
            return True
        for election in self.elections:
            if election.has_results:
                return True
        return False 
    @property
[docs]
    def elected_candidates(self) -> list[tuple[str, str]]:
        """ Returns the first and last names of the elected candidates. """
        result = []
        for election in self.elections:
            result.extend(election.elected_candidates)
        return result 
    @property
[docs]
    def relationships_for_historical_party_results(
        self
    ) -> Query[ElectionCompoundRelationship]:
        return self.election_compound.related_compounds