#!/usr/bin/env python3
"""
Flask-Server: Kantinen-Speiseplan als abonnierbare iCal-URL.
Täglicher Hintergrund-Refresh lädt neue PDFs und aktualisiert den Kalender.
"""

import logging
import os
import threading
import time
from datetime import date, datetime
from zoneinfo import ZoneInfo

from flask import Flask, Response, render_template, request
from werkzeug.middleware.proxy_fix import ProxyFix

from kantine2ical import BASE_URL, empty_ical_bytes, refresh_speiseplan

# Konfiguration (Umgebungsvariablen mit Fallback)
KANTINE_BASE_URL = os.environ.get("KANTINE_BASE_URL", BASE_URL)
REFRESH_INTERVAL_SECONDS = int(os.environ.get("REFRESH_INTERVAL_SECONDS", "43200"))  # 12h
# Öffentliche Basis-URL (z. B. https://kantine.elpatron.me), wenn Proxy keine X-Forwarded-* sendet
PUBLIC_URL = os.environ.get("PUBLIC_URL", "").strip().rstrip("/")

# Template-Ordner immer relativ zu dieser Datei (funktioniert mit Gunicorn/Docker)
_template_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
app = Flask(__name__, template_folder=_template_dir)
# Hinter Reverse-Proxy (HTTPS): X-Forwarded-Proto und X-Forwarded-Host nutzen
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
_log = logging.getLogger(__name__)

# Cache: zuletzt gültige iCal-Bytes; strukturierte Daten (Datum → Gerichte); Zeitpunkt der letzten Aktualisierung
_ical_cache: bytes = empty_ical_bytes()
_by_date_cache: dict[date, list[str]] | None = None
_last_refresh_at: datetime | None = None
_cache_lock = threading.Lock()
_TIMEZONE = ZoneInfo("Europe/Berlin")
_WEEKDAY_NAMES = ("Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag")

def _do_refresh() -> None:
    """Refresh ausführen und bei Erfolg Cache aktualisieren."""
    global _ical_cache, _by_date_cache, _last_refresh_at
    result = refresh_speiseplan(KANTINE_BASE_URL)
    if result is not None:
        by_date, ical_bytes = result
        with _cache_lock:
            _ical_cache = ical_bytes
            _by_date_cache = by_date
            _last_refresh_at = datetime.now(_TIMEZONE)
        _log.info("Speiseplan-Refresh erfolgreich, Cache aktualisiert.")
    else:
        _log.warning("Speiseplan-Refresh fehlgeschlagen oder keine Daten; Cache unverändert.")


def _refresh_loop() -> None:
    """Hintergrund-Thread: alle REFRESH_INTERVAL_SECONDS einen Refresh ausführen."""
    while True:
        time.sleep(REFRESH_INTERVAL_SECONDS)
        try:
            _do_refresh()
        except Exception as e:
            _log.exception("Refresh-Thread: %s", e)


# Beim Import: einmal Refresh, Hintergrund-Thread starten (gilt auch für Gunicorn)
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
_log.info("Starte Speiseplan-Refresh beim Start ...")
_do_refresh()
_refresh_thread = threading.Thread(target=_refresh_loop, daemon=True)
_refresh_thread.start()
_log.info("Hintergrund-Refresh alle %s Sekunden.", REFRESH_INTERVAL_SECONDS)


@app.route("/refresh")
def refresh() -> Response:
    """Manuellen Refresh auslösen (z. B. nach neuem Speiseplan auf kantine-bhz.de)."""
    _log.info("Manueller Refresh ausgelöst")
    _do_refresh()
    last = _format_last_refresh()
    return Response(
        f"Refresh ausgeführt. Zuletzt aktualisiert: {last or '–'}.\n",
        mimetype="text/plain; charset=utf-8",
    )


@app.route("/calendar.ics")
def calendar_ics() -> Response:
    """iCal-Kalender ausliefern (für Abo-URL z. B. in Google Kalender)."""
    with _cache_lock:
        data = _ical_cache
    return Response(
        data,
        mimetype="text/calendar; charset=utf-8",
        headers={"Content-Disposition": 'attachment; filename="kantine_speiseplan.ics"'},
    )


def _format_last_refresh() -> str | None:
    """Zeitpunkt der letzten Aktualisierung formatiert (z. B. 29.01.2025, 14:32 Uhr)."""
    with _cache_lock:
        t = _last_refresh_at
    if t is None:
        return None
    return t.strftime("%d.%m.%Y, %H:%M") + " Uhr"


def _upcoming_days() -> list[tuple[str, list[str]]]:
    """Heute und zukünftige Tage aus dem Cache, sortiert, mit formatiertem Datum (z. B. Montag, 03.02.2026)."""
    with _cache_lock:
        by_date = _by_date_cache
    if not by_date:
        return []
    today = datetime.now(_TIMEZONE).date()
    upcoming = [(d, dishes) for d, dishes in by_date.items() if d >= today]
    upcoming.sort(key=lambda x: x[0])
    return [
        (f"{_WEEKDAY_NAMES[d.weekday()]}, {d.strftime('%d.%m.%Y')}", dishes)
        for d, dishes in upcoming
    ]


@app.route("/")
def index():
    """Startseite mit Anleitung zur iCal-Einbettung (Google und andere) und Speiseplan-Kacheln."""
    base = PUBLIC_URL or request.url_root.rstrip("/")
    calendar_url = f"{base}/calendar.ics"
    last_refresh_str = _format_last_refresh()
    upcoming_days = _upcoming_days()
    try:
        return render_template(
            "index.html",
            calendar_url=calendar_url,
            last_refresh_str=last_refresh_str,
            upcoming_days=upcoming_days,
        )
    except Exception as e:
        _log.exception("Template index.html: %s", e)
        return (
            f'<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><title>Speiseplan iCal</title></head>'
            f'<body><h1>Speiseplan Kantine BHZ Kiel-Wik</h1>'
            f'<p>Zuletzt aktualisiert: {last_refresh_str or "noch nicht"}.</p>'
            f'<p>Abo-URL: <a href="{calendar_url}">{calendar_url}</a></p>'
            f'<p>In Google Kalender: Andere Kalender → Von URL → obige URL einfügen.</p></body></html>',
            200,
            {"Content-Type": "text/html; charset=utf-8"},
        )


def main() -> None:
    """Flask-Entwicklungsserver starten (Refresh und Thread laufen bereits beim Import)."""
    app.run(host="0.0.0.0", port=5000)


if __name__ == "__main__":
    main()
