from __future__ import annotations

import locale
import os
import re
from datetime import date, datetime


class DateValidationError(ValueError):
    pass


def setup_system_locale() -> None:
    try:
        locale.setlocale(locale.LC_TIME, "")
    except locale.Error:
        pass


def system_date_pattern() -> str:
    try:
        system_pattern = locale.nl_langinfo(locale.D_FMT)
    except (AttributeError, ValueError):
        system_pattern = ""
    year_position = system_pattern.find("%Y")
    day_position = system_pattern.find("%d")
    month_position = system_pattern.find("%m")
    if (
        year_position >= 0
        and day_position >= 0
        and month_position >= 0
        and year_position < min(day_position, month_position)
    ):
        return "%Y-%m-%d"
    if day_position >= 0 and month_position >= 0 and day_position < month_position:
        return "%d.%m.%Y"
    locale_hint = " ".join(
        filter(
            None,
            (
                os.environ.get("LC_TIME"),
                os.environ.get("LANGUAGE"),
                os.environ.get("LANG"),
            ),
        )
    ).lower()
    day_first_languages = ("de", "at", "ch", "fr", "it", "es", "pt", "nl", "pl", "cs")
    return "%d.%m.%Y" if locale_hint.startswith(day_first_languages) else "%Y-%m-%d"


def date_input_hint() -> str:
    return "DD.MM.YYYY" if system_date_pattern() == "%d.%m.%Y" else "YYYY-MM-DD"


def parse_date_input(value: str, field_name: str, *, allow_empty: bool = True) -> date | None:
    text = value.strip()
    if not text:
        if allow_empty:
            return None
        raise DateValidationError(f"{field_name} ist erforderlich.")
    patterns = (system_date_pattern(), "%Y-%m-%d", "%d.%m.%Y")
    for pattern in dict.fromkeys(patterns):
        expected = r"\d{2}\.\d{2}\.\d{4}" if pattern == "%d.%m.%Y" else r"\d{4}-\d{2}-\d{2}"
        if not re.fullmatch(expected, text):
            continue
        try:
            return datetime.strptime(text, pattern).date()
        except ValueError:
            continue
    formats = date_input_hint()
    if formats != "YYYY-MM-DD":
        formats += " oder YYYY-MM-DD"
    raise DateValidationError(f"{field_name} muss ein gültiges Datum im Format {formats} sein.")


def normalize_date_input(value: str, field_name: str) -> str:
    parsed = parse_date_input(value, field_name)
    return parsed.isoformat() if parsed else ""


def month_input_hint() -> str:
    return "MM.YYYY" if system_date_pattern() == "%d.%m.%Y" else "YYYY-MM"


def parse_month_input(value: str, field_name: str, *, allow_empty: bool = True) -> str:
    """Parses a month-only value (no day) and normalizes it to 'YYYY-MM', which also
    sorts and compares correctly as a plain string."""
    text = value.strip()
    if not text:
        if allow_empty:
            return ""
        raise DateValidationError(f"{field_name} ist erforderlich.")
    for pattern, expected in (("%m.%Y", r"\d{2}\.\d{4}"), ("%Y-%m", r"\d{4}-\d{2}")):
        if not re.fullmatch(expected, text):
            continue
        try:
            parsed = datetime.strptime(text, pattern)
        except ValueError:
            continue
        return f"{parsed.year:04d}-{parsed.month:02d}"
    raise DateValidationError(f"{field_name} muss ein gültiger Monat im Format {month_input_hint()} sein.")


def format_month_for_display(value: str) -> str:
    text = value.strip()
    match = re.fullmatch(r"(\d{4})-(\d{2})", text)
    if not match:
        return text
    year, month = match.groups()
    return f"{month}.{year}" if system_date_pattern() == "%d.%m.%Y" else f"{year}-{month}"


def format_date_for_display(value: str) -> str:
    text = value.strip()
    if not text:
        return ""
    try:
        parsed = parse_iso_date(text, "Datum")
    except DateValidationError:
        return text
    return parsed.strftime(system_date_pattern()) if parsed else ""


def parse_iso_date(value: str, field_name: str, *, allow_empty: bool = True) -> date | None:
    text = value.strip()
    if not text:
        if allow_empty:
            return None
        raise DateValidationError(f"{field_name} ist erforderlich.")
    if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", text):
        raise DateValidationError(f"{field_name} muss das Format JJJJ-MM-TT haben.")
    try:
        return date.fromisoformat(text)
    except ValueError as exc:
        raise DateValidationError(f"{field_name} ist kein gültiges Kalenderdatum.") from exc


def validate_birth_date(value: str, *, today: date | None = None) -> date | None:
    parsed = parse_iso_date(value, "Geburtsdatum")
    if parsed is None:
        return None
    reference = today or date.today()
    _validate_birth_date_value(parsed, reference)
    return parsed


def validate_member_dates(
    *,
    birth_date: str,
    accepted_at: str = "",
    membership_started_at: str = "",
    membership_ended_at: str = "",
    today: date | None = None,
) -> None:
    reference = today or date.today()
    birth = validate_birth_date(birth_date, today=reference)
    accepted = _validate_not_future(accepted_at, "Aufnahmebeschluss", reference)
    started = _validate_not_future(membership_started_at, "Mitglied seit", reference)
    ended = parse_iso_date(membership_ended_at, "Austrittsdatum")
    if birth and accepted and accepted < birth:
        raise DateValidationError("Aufnahmebeschluss darf nicht vor dem Geburtsdatum liegen.")
    if birth and started and started < birth:
        raise DateValidationError("Mitgliedschaft darf nicht vor dem Geburtsdatum beginnen.")
    if accepted and started and started < accepted:
        raise DateValidationError("Mitgliedschaft darf nicht vor dem Aufnahmebeschluss beginnen.")
    if started and ended and ended < started:
        raise DateValidationError("Austrittsdatum darf nicht vor dem Mitgliedschaftsbeginn liegen.")
    if accepted and ended and ended < accepted:
        raise DateValidationError("Austrittsdatum darf nicht vor dem Aufnahmebeschluss liegen.")


def calculate_age(birth_date: date, on_date: date | None = None) -> int:
    reference = on_date or date.today()
    return (
        reference.year
        - birth_date.year
        - ((reference.month, reference.day) < (birth_date.month, birth_date.day))
    )


def age_label(value: str, *, today: date | None = None) -> str:
    if not value.strip():
        return "Alter: —"
    try:
        parsed = parse_date_input(value, "Geburtsdatum")
        if parsed:
            _validate_birth_date_value(parsed, today or date.today())
    except DateValidationError:
        return "UNGÜLTIGES DATUM"
    return f"Alter: {calculate_age(parsed, today)} Jahre" if parsed else "Alter: —"


def _validate_not_future(value: str, field_name: str, reference: date) -> date | None:
    parsed = parse_iso_date(value, field_name)
    if parsed and parsed > reference:
        raise DateValidationError(f"{field_name} darf nicht in der Zukunft liegen.")
    return parsed


def _validate_birth_date_value(parsed: date, reference: date) -> None:
    if parsed > reference:
        raise DateValidationError("Geburtsdatum darf nicht in der Zukunft liegen.")
    if calculate_age(parsed, reference) > 120:
        raise DateValidationError("Geburtsdatum ist unplausibel: Das berechnete Alter liegt über 120.")
