"""Nutzungsverhaeltnisse – die einzige Stelle, an der Miet- und
Eigentumsverhaeltnisse zusammengefuehrt werden.

Fachliche Vorgabe (xin, HANDOVER_vportal_nutzungsverhaeltnisse.md):

    Im Portal gibt es keine Eigentuemer und keine Mieter, sondern
    ausschliesslich NUTZER. Wer Nutzer ist, ergibt sich aus der
    Nutzungsperiode. Der Eigentuemer bekommt keine Sonderrolle.

Gespeichert wird analog zu xin in zwei Tabellen (``ReuMietV`` /
``ReuEigentuemer``), gelesen wird ausschliesslich ueber dieses Modul. Jede
Zugriffspruefung, jede Liste und jede Ueberlappungspruefung geht hier durch –
sonst laufen die Regeln fuer Mieter und Eigentuemer auseinander.

Sichtbarkeitsregel: unveraendert wie bisher fuer Mietvertraege, jetzt auch
fuer Eigentumsverhaeltnisse. Ein Verhaeltnis zaehlt, wenn es am Stichtag
laeuft:

    GueltigVon <= heute  UND  (GueltigBis IS NULL ODER GueltigBis >= heute)

Die 24-Monats-Regel aus Arbeitspaket C ist hier bewusst NICHT enthalten. Sie
wuerde beendete Verhaeltnisse wieder sichtbar machen und gehoert in einen
eigenen Schritt.
"""
from __future__ import annotations

from dataclasses import dataclass
from datetime import date
from typing import Optional, Sequence

from django.db.models import Q
from django.utils import timezone

from app_nutzer.models import ReuEigentuemer, ReuMietV

ART_MIETER = 'mieter'
ART_EIGENTUEMER = 'eigentuemer'


@dataclass(frozen=True)
class Nutzungsverhaeltnis:
    """Ein Verhaeltnis Person <-> Einheit, unabhaengig von der Quelltabelle."""

    art: str                        # ART_MIETER | ART_EIGENTUEMER
    reuid: int
    personen_id: int
    gueltig_von: Optional[date]
    gueltig_bis: Optional[date]     # None = offen
    quell_id: int                   # MietVID bzw. EigentumID

    @property
    def schluessel(self) -> str:
        """Stabiler Schluessel fuer URLs und Dropdown-Auswahl."""
        return f'{self.art}:{self.quell_id}'

    @property
    def ist_eigentuemer(self) -> bool:
        return self.art == ART_EIGENTUEMER

    def ist_aktiv(self, stichtag: Optional[date] = None) -> bool:
        """Laeuft das Verhaeltnis am Stichtag?"""
        tag = stichtag or timezone.now().date()
        if self.gueltig_von and self.gueltig_von > tag:
            return False
        if self.gueltig_bis and self.gueltig_bis < tag:
            return False
        return True

    def ueberlappt(self, von: Optional[date], bis: Optional[date]) -> bool:
        """Schneidet sich dieses Verhaeltnis mit dem Zeitraum [von, bis]?

        Offene Enden (``None``) gelten als unendlich.
        """
        if von is not None and self.gueltig_bis is not None and self.gueltig_bis < von:
            return False
        if bis is not None and self.gueltig_von is not None and self.gueltig_von > bis:
            return False
        return True


# ---------------------------------------------------------------------------
# Laden
# ---------------------------------------------------------------------------

def _aus_mietv(mv: ReuMietV) -> Nutzungsverhaeltnis:
    return Nutzungsverhaeltnis(
        art=ART_MIETER,
        reuid=mv.REUID_id,
        personen_id=mv.MieterID,
        gueltig_von=mv.ContractValidFrom,
        gueltig_bis=mv.ContractValidUntil,
        quell_id=mv.MietVID,
    )


def _aus_eigentum(et: ReuEigentuemer) -> Nutzungsverhaeltnis:
    return Nutzungsverhaeltnis(
        art=ART_EIGENTUEMER,
        reuid=et.REUID_id,
        personen_id=et.EigentuemerID,
        gueltig_von=et.ValidFrom,
        gueltig_bis=et.ValidUntil,
        quell_id=et.EigentumID,
    )


def _sortierschluessel(nv: Nutzungsverhaeltnis):
    # Chronologisch je Einheit; stabil ueber (art, quell_id).
    return (nv.reuid, nv.gueltig_von or date.min, nv.art, nv.quell_id)


def lade_verhaeltnisse(
    *,
    personen_ids: Optional[Sequence[int]] = None,
    reuids: Optional[Sequence[int]] = None,
    nur_aktive: bool = True,
    stichtag: Optional[date] = None,
) -> list[Nutzungsverhaeltnis]:
    """Vereinigt Miet- und Eigentumsverhaeltnisse zu einer Liste.

    ``personen_ids=None`` bedeutet "alle Personen" (Admin-Sicht),
    ``personen_ids=[]`` bedeutet "keine" und liefert eine leere Liste.
    """
    if personen_ids is not None and len(personen_ids) == 0:
        return []

    tag = stichtag or timezone.now().date()

    mietv_qs = ReuMietV.objects.all()
    eigentum_qs = ReuEigentuemer.objects.all()

    if personen_ids is not None:
        mietv_qs = mietv_qs.filter(MieterID__in=personen_ids)
        eigentum_qs = eigentum_qs.filter(EigentuemerID__in=personen_ids)

    if reuids is not None:
        mietv_qs = mietv_qs.filter(REUID__in=reuids)
        eigentum_qs = eigentum_qs.filter(REUID__in=reuids)

    if nur_aktive:
        mietv_qs = mietv_qs.filter(ContractValidFrom__lte=tag).filter(
            Q(ContractValidUntil__isnull=True) | Q(ContractValidUntil__gte=tag)
        )
        eigentum_qs = eigentum_qs.filter(ValidFrom__lte=tag).filter(
            Q(ValidUntil__isnull=True) | Q(ValidUntil__gte=tag)
        )

    verhaeltnisse = [_aus_mietv(mv) for mv in mietv_qs]
    verhaeltnisse += [_aus_eigentum(et) for et in eigentum_qs]

    # Datensaetze ohne Person oder ohne Einheit sind unbrauchbar.
    verhaeltnisse = [
        nv for nv in verhaeltnisse
        if nv.personen_id is not None and nv.reuid is not None
    ]

    verhaeltnisse.sort(key=_sortierschluessel)
    return verhaeltnisse


def verhaeltnisse_fuer_user(
    user,
    *,
    nur_aktive: bool = True,
    stichtag: Optional[date] = None,
) -> list[Nutzungsverhaeltnis]:
    """Alle Verhaeltnisse dieses Nutzers – als Mieter wie als Eigentuemer."""
    personen_id = getattr(user, 'PersonenID', None)
    if personen_id is None:
        return []
    return lade_verhaeltnisse(
        personen_ids=[personen_id], nur_aktive=nur_aktive, stichtag=stichtag
    )


def reuids_fuer_user(user, *, stichtag: Optional[date] = None) -> list[int]:
    """Einheiten, die dieser Nutzer sehen darf – ohne Duplikate."""
    gesehen: dict = {}
    for nv in verhaeltnisse_fuer_user(user, stichtag=stichtag):
        gesehen.setdefault(nv.reuid, None)
    return list(gesehen)


def hat_zugriff(user, reuid: int, *, stichtag: Optional[date] = None) -> bool:
    return any(
        nv.reuid == int(reuid)
        for nv in verhaeltnisse_fuer_user(user, stichtag=stichtag)
    )


# ---------------------------------------------------------------------------
# Ueberlappungspruefung (xin, Arbeitspaket A)
# ---------------------------------------------------------------------------

def ueberlappende_verhaeltnisse(
    reuid: int,
    von: Optional[date],
    bis: Optional[date],
    *,
    ausser: Optional[tuple] = None,
) -> list[Nutzungsverhaeltnis]:
    """Verhaeltnisse derselben Einheit, die sich mit [von, bis] schneiden.

    ``ausser=(art, quell_id)`` blendet den Datensatz aus, der gerade
    geschrieben wird – sonst kollidiert jedes Update mit sich selbst.

    Geprueft wird gegen Nutzungsperioden und nicht gegen Mietvertraege: sonst
    bleibt im WEG-Fall der Eigentuemer-Nutzer unsichtbar und Ueberlappungen
    Eigentuemer <-> Mieter kaemen durch.
    """
    kandidaten = lade_verhaeltnisse(reuids=[reuid], nur_aktive=False)
    treffer = []
    for nv in kandidaten:
        if ausser is not None and (nv.art, nv.quell_id) == tuple(ausser):
            continue
        if nv.ueberlappt(von, bis):
            treffer.append(nv)
    return treffer
