from __future__ import annotations

from dataclasses import dataclass, field
from datetime import date, datetime
from decimal import Decimal
from typing import Any


def _iso_now() -> str:
    return datetime.now().astimezone().isoformat(timespec="seconds")


MEMBERSHIP_STATUS_LABELS = {
    "application": "ANTRAG",
    "accepted_pending_payment": "ANGENOMMEN / ZAHLUNG OFFEN",
    "active": "AKTIV",
    "suspended_contribution": "RUHEND / BEITRAG",
    "resigned_end_of_year": "AUSTRITT ZUM JAHRESENDE",
    "honorary": "EHRENMITGLIED",
    "ended": "BEENDET",
}

PAYMENT_FREQUENCY_LABELS = {
    "monthly": "MONATLICH",
    "quarterly": "QUARTALSWEISE",
    "semiannual": "HALBJÄHRLICH",
    "annual": "JÄHRLICH",
}

ASSET_STATUS_LABELS = {
    "available": "VERFUEGBAR",
    "issued": "AUSGEGEBEN",
    "lost": "VERLOREN",
    "retired": "AUSGEMUSTERT",
}

ASSET_OWNER_TYPE_LABELS = {
    "club": "VEREIN",
    "member": "MITGLIED",
    "external": "EXTERN",
}

ASSET_CUSTODY_TYPE_LABELS = {
    "club": "VEREIN",
    "member": "MITGLIED",
    "external": "EXTERN",
    "unknown": "UNBEKANNT",
}

HOUSEKEEPER_MEMBER_FIELD_LABELS = {
    "nickname": "Nickname",
    "email": "E-Mail-Adresse",
    "phone": "Telefonnummer",
    "birth_date": "Geburtsdatum",
    "street": "Straße und Hausnummer",
    "postal_code": "Postleitzahl",
    "city": "Ort",
    "country": "Land",
    "accepted_at": "Aufnahmebeschluss",
    "membership_started_at": "Mitglied seit",
}

DEFAULT_OPTIONAL_MEMBER_FIELDS = tuple(
    field for field in HOUSEKEEPER_MEMBER_FIELD_LABELS if field != "birth_date"
)


def normalize_optional_member_fields(values: Any) -> tuple[str, ...]:
    if not isinstance(values, (list, tuple, set, frozenset)):
        return ()
    selected = {str(value).strip() for value in values}
    return tuple(field for field in HOUSEKEEPER_MEMBER_FIELD_LABELS if field in selected)


@dataclass(slots=True)
class Member:
    member_id: str
    member_number: str
    first_name: str
    last_name: str
    nickname: str = ""
    email: str = ""
    phone: str = ""
    birth_date: str = ""
    street: str = ""
    address_addition: str = ""
    postal_code: str = ""
    city: str = ""
    country: str = "Deutschland"
    account_holder: str = ""
    iban: str = ""
    bic: str = ""
    mandate_reference: str = ""
    mandate_signed_at: str = ""
    mandate_active: bool = False
    mandate_revoked_at: str = ""
    status: str = "application"
    application_date: str = ""
    accepted_at: str = ""
    membership_started_at: str = ""
    membership_ended_at: str = ""
    payment_frequency: str = "semiannual"
    contribution_rule_id: str = "standard-2022"
    honorary: bool = False
    notes: str = ""
    created_at: str = field(default_factory=_iso_now)
    updated_at: str = field(default_factory=_iso_now)
    schema_version: int = 1

    @property
    def display_name(self) -> str:
        return " ".join(part for part in (self.first_name, self.last_name) if part).strip()

    def to_dict(self) -> dict[str, Any]:
        return {
            "schema_version": self.schema_version,
            "member_id": self.member_id,
            "member_number": self.member_number,
            "person": {
                "first_name": self.first_name,
                "last_name": self.last_name,
                "nickname": self.nickname,
                "birth_date": self.birth_date,
                "email": self.email,
                "phone": self.phone,
            },
            "address": {
                "street": self.street,
                "addition": self.address_addition,
                "postal_code": self.postal_code,
                "city": self.city,
                "country": self.country,
            },
            "banking": {
                "account_holder": self.account_holder,
                "iban": self.iban,
                "bic": self.bic,
                "mandate_reference": self.mandate_reference,
                "mandate_signed_at": self.mandate_signed_at,
                "mandate_active": self.mandate_active,
                "mandate_revoked_at": self.mandate_revoked_at,
            },
            "membership": {
                "status": self.status,
                "application_date": self.application_date,
                "accepted_at": self.accepted_at,
                "started_at": self.membership_started_at,
                "ended_at": self.membership_ended_at,
                "honorary": self.honorary,
            },
            "contribution_profile": {
                "rule_id": self.contribution_rule_id,
                "payment_frequency": self.payment_frequency,
            },
            "notes": self.notes,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Member:
        person = data.get("person") or {}
        membership = data.get("membership") or {}
        contribution = data.get("contribution_profile") or {}
        address = data.get("address") or {}
        banking = data.get("banking") or {}
        return cls(
            schema_version=int(data.get("schema_version", 1)),
            member_id=str(data["member_id"]),
            member_number=str(data.get("member_number", "")),
            first_name=str(person.get("first_name", "")),
            last_name=str(person.get("last_name", "")),
            nickname=str(person.get("nickname", "")),
            email=str(person.get("email", "")),
            phone=str(person.get("phone", "")),
            birth_date=str(person.get("birth_date", "")),
            street=str(address.get("street", "")),
            address_addition=str(address.get("addition", "")),
            postal_code=str(address.get("postal_code", "")),
            city=str(address.get("city", "")),
            country=str(address.get("country", "Deutschland")),
            account_holder=str(banking.get("account_holder", "")),
            iban=str(banking.get("iban", "")),
            bic=str(banking.get("bic", "")),
            mandate_reference=str(banking.get("mandate_reference", "")),
            mandate_signed_at=str(banking.get("mandate_signed_at", "")),
            mandate_active=bool(banking.get("mandate_active", False)),
            mandate_revoked_at=str(banking.get("mandate_revoked_at", "")),
            status=str(membership.get("status", "application")),
            application_date=str(membership.get("application_date", "")),
            accepted_at=str(membership.get("accepted_at", "")),
            membership_started_at=str(membership.get("started_at", "")),
            membership_ended_at=str(membership.get("ended_at", "")),
            honorary=bool(membership.get("honorary", False)),
            contribution_rule_id=str(contribution.get("rule_id", "standard-2022")),
            payment_frequency=str(contribution.get("payment_frequency", "semiannual")),
            notes=str(data.get("notes", "")),
            created_at=str(data.get("created_at", _iso_now())),
            updated_at=str(data.get("updated_at", _iso_now())),
        )


@dataclass(slots=True)
class Asset:
    asset_id: str
    label: str
    category: str = ""
    inventory_number: str = ""
    serial_number: str = ""
    status: str = "available"
    current_holder_member_id: str = ""
    owner_type: str = "club"
    owner_member_id: str = ""
    owner_name: str = ""
    custody_type: str = "club"
    location: str = ""
    handed_over_at: str = ""
    condition: str = ""
    estimated_value: str = ""
    deposit_amount_default: str = "0.00"
    notes: str = ""
    created_at: str = field(default_factory=_iso_now)
    updated_at: str = field(default_factory=_iso_now)
    schema_version: int = 1

    def to_dict(self) -> dict[str, Any]:
        return {
            "schema_version": self.schema_version,
            "asset_id": self.asset_id,
            "label": self.label,
            "category": self.category,
            "inventory_number": self.inventory_number,
            "serial_number": self.serial_number,
            "status": self.status,
            "current_holder_member_id": self.current_holder_member_id,
            "owner_type": self.owner_type,
            "owner_member_id": self.owner_member_id,
            "owner_name": self.owner_name,
            "custody_type": self.custody_type,
            "location": self.location,
            "handed_over_at": self.handed_over_at,
            "condition": self.condition,
            "estimated_value": self.estimated_value,
            "deposit_amount_default": self.deposit_amount_default,
            "notes": self.notes,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Asset:
        return cls(
            schema_version=int(data.get("schema_version", 1)),
            asset_id=str(data["asset_id"]),
            label=str(data.get("label", "")),
            category=str(data.get("category", "")),
            inventory_number=str(data.get("inventory_number", "")),
            serial_number=str(data.get("serial_number", "")),
            status=str(data.get("status", "available")),
            current_holder_member_id=str(data.get("current_holder_member_id", "")),
            owner_type=str(data.get("owner_type", "club")),
            owner_member_id=str(data.get("owner_member_id", "")),
            owner_name=str(data.get("owner_name", "")),
            custody_type=str(
                data.get(
                    "custody_type",
                    "member" if data.get("current_holder_member_id") else "club",
                )
            ),
            location=str(data.get("location", "")),
            handed_over_at=str(data.get("handed_over_at", "")),
            condition=str(data.get("condition", "")),
            estimated_value=str(data.get("estimated_value", "")),
            deposit_amount_default=str(data.get("deposit_amount_default", "0.00")),
            notes=str(data.get("notes", "")),
            created_at=str(data.get("created_at", _iso_now())),
            updated_at=str(data.get("updated_at", _iso_now())),
        )


@dataclass(slots=True)
class Event:
    event_id: str
    timestamp: str
    event_type: str
    summary: str
    actor_type: str = "system"
    actor_name: str = "CCMA"
    references: dict[str, str] = field(default_factory=dict)
    data: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        return {
            "schema_version": 1,
            "event_id": self.event_id,
            "timestamp": self.timestamp,
            "type": self.event_type,
            "actor": {"type": self.actor_type, "name": self.actor_name},
            "summary": self.summary,
            "references": self.references,
            "data": self.data,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Event:
        actor = data.get("actor") or {}
        return cls(
            event_id=str(data["event_id"]),
            timestamp=str(data["timestamp"]),
            event_type=str(data.get("type", "unknown")),
            summary=str(data.get("summary", "")),
            actor_type=str(actor.get("type", "system")),
            actor_name=str(actor.get("name", "CCMA")),
            references=dict(data.get("references") or {}),
            data=dict(data.get("data") or {}),
        )


@dataclass(slots=True)
class ContributionData:
    claims: list[dict[str, Any]] = field(default_factory=list)
    payments: list[dict[str, Any]] = field(default_factory=list)
    credits: list[dict[str, Any]] = field(default_factory=list)
    allocations: list[dict[str, Any]] = field(default_factory=list)
    reminders: list[dict[str, Any]] = field(default_factory=list)
    donations: list[dict[str, Any]] = field(default_factory=list)
    contribution_overrides: list[dict[str, Any]] = field(default_factory=list)
    schema_version: int = 1

    def to_dict(self) -> dict[str, Any]:
        return {
            "schema_version": self.schema_version,
            "claims": self.claims,
            "payments": self.payments,
            "credits": self.credits,
            "allocations": self.allocations,
            "reminders": self.reminders,
            "donations": self.donations,
            "contribution_overrides": self.contribution_overrides,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ContributionData:
        return cls(
            schema_version=int(data.get("schema_version", 1)),
            claims=list(data.get("claims") or []),
            payments=list(data.get("payments") or []),
            credits=list(data.get("credits") or []),
            allocations=list(data.get("allocations") or []),
            reminders=list(data.get("reminders") or []),
            donations=list(data.get("donations") or []),
            contribution_overrides=list(data.get("contribution_overrides") or []),
        )


@dataclass(frozen=True, slots=True)
class HousekeeperFinding:
    severity: str
    code: str
    title: str
    detail: str
    member_id: str = ""
    asset_id: str = ""
    target_type: str = "member"
    due_date: date | None = None
    key: str = ""


def money(value: str | int | float | Decimal) -> Decimal:
    return Decimal(str(value)).quantize(Decimal("0.01"))
