import os
import sqlite3
import uuid
from hmac import compare_digest
from datetime import datetime
from functools import wraps
from pathlib import Path

from flask import (
    Flask,
    flash,
    g,
    redirect,
    render_template,
    request,
    send_from_directory,
    session,
    url_for,
)
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename

app = Flask(__name__)
base_dir = Path(__file__).resolve().parent
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "change-me-in-production")
app.config["DB_PATH"] = os.environ.get("DB_PATH", str(base_dir / "app.sqlite3"))
app.config["UPLOAD_FOLDER"] = os.environ.get("UPLOAD_FOLDER", str(base_dir / "uploads"))
app.config["EVENT_PASSWORD"] = os.environ.get("EVENT_PASSWORD", "wedding2026")
app.config["HOST_PASSWORD"] = os.environ.get("HOST_PASSWORD", "gastgeber2026")
app.config["MAX_CONTENT_LENGTH"] = int(os.environ.get("MAX_UPLOAD_BYTES", str(64 * 1024 * 1024)))
app.config["LOCATION_NAME"] = os.environ.get("LOCATION_NAME", "Schlossgarten Venue")
app.config["LOCATION_ADDRESS"] = os.environ.get(
    "LOCATION_ADDRESS", "Musterstraße 12, 12345 Musterstadt"
)
app.config["LOCATION_WEBSITE_URL"] = os.environ.get(
    "LOCATION_WEBSITE_URL", "https://example.com/location"
)
app.config["GOOGLE_MAPS_EMBED_URL"] = os.environ.get(
    "GOOGLE_MAPS_EMBED_URL",
    "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d0",
)
app.config["WEDDING_DATE"] = os.environ.get("WEDDING_DATE", "")
app.config["HERO_IMAGE_FILENAME"] = os.environ.get("HERO_IMAGE_FILENAME")

ALLOWED_EXTENSIONS = {"jpg", "jpeg", "png", "heic", "heif"}
ALLOWED_MIME_TYPES = {
    "image/jpeg",
    "image/jpg",
    "image/png",
    "image/heic",
    "image/heif",
    "image/heic-sequence",
    "image/heif-sequence",
}

TEXTS = {
    "de": {
        "brand": "Svenja & Dominic",
        "subtitle": "Willkommen zu unserer Hochzeits-App",
        "login_note": "Passwortgeschützter Zugriff für unsere Gäste.",
        "login": "Login",
        "name": "Dein Name",
        "event_password": "Event-Passwort",
        "login_submit": "Weiter zum Gästebereich",
        "guest_area": "Gästebereich",
        "hello_guest": "Hallo {name}.",
        "logout": "Abmelden",
        "rsvp": "RSVP",
        "upload": "Upload",
        "gallery": "Galerie",
        "host_area": "Gastgeberbereich",
        "info": "Infos",
        "save": "Speichern",
        "attending": "Ich komme",
        "not_attending": "Ich komme nicht",
        "plus_one": "Ich bringe eine Begleitperson mit",
        "file": "Bild auswählen",
        "add_more_files": "Weitere Datei hinzufügen",
        "upload_multi_hint": "Du kannst mehrere Bilder auf einmal auswählen oder weitere Felder hinzufügen.",
        "upload_selected_count": "{count} Bilder ausgewählt",
        "upload_submit": "Foto hochladen",
        "schedule": "Ablauf",
        "hotels": "Hotels",
        "taxi": "Taxi",
        "location": "Location",
        "visit_location": "Zur Location-Webseite",
        "privacy": "Datenschutz",
        "imprint": "Impressum",
        "hero_headline": "Willkommen zu unserer Hochzeit",
        "hero_text": "Wir freuen uns riesig, diesen besonderen Tag mit euch zu feiern.",
        "to_guest_area": "Zum Gästebereich",
        "schedule_text": "15:00 Trauung, 17:00 Empfang, 19:00 Dinner.",
        "hotels_text": "Empfehlungen folgen. Bitte frühzeitig buchen.",
        "taxi_text": "Taxi-Service: 01234 / 567890 (24/7).",
        "gallery_uploaded_by": "von {name}",
        "gallery_empty": "Noch keine Bilder vorhanden.",
        "gallery_image_alt": "Upload von {name}",
        "flash_enter_name": "Bitte Namen eingeben.",
        "flash_invalid_password": "Ungültiges Event-Passwort.",
        "flash_rsvp_select": "Bitte eine RSVP-Auswahl treffen.",
        "flash_rsvp_saved": "RSVP gespeichert.",
        "flash_select_image": "Bitte eine Bilddatei auswählen.",
        "flash_allowed_types": "Nur JPG/JPEG/PNG/HEIC/HEIF sind erlaubt.",
        "flash_upload_success": "Upload erfolgreich.",
        "flash_upload_success_count": "{count} Bilder erfolgreich hochgeladen.",
        "flash_upload_too_large": "Upload zu groß. Bitte in kleineren Paketen hochladen (max. {max_mb} MB pro Anfrage).",
        "flash_upload_failed": "Upload fehlgeschlagen. Bitte erneut versuchen.",
        "flash_invalid_host_password": "Ungültiges Gastgeber-Passwort.",
        "host_access_title": "Gastgeberbereich",
        "host_access_note": "Dieser Bereich ist nur für das Brautpaar vorgesehen.",
        "host_password": "Gastgeber-Passwort",
        "host_access_submit": "Adminbereich öffnen",
        "host_stats_title": "Übersicht",
        "total_guests": "Gäste gesamt",
        "attending_yes": "Zusagen",
        "attending_no": "Absagen",
        "attending_open": "Noch offen",
        "plus_one_total": "Begleitpersonen",
        "host_table_name": "Name",
        "host_table_status": "RSVP",
        "host_table_plus_one": "Begleitperson",
        "status_yes": "Kommt",
        "status_no": "Kommt nicht",
        "status_open": "Offen",
        "yes": "Ja",
        "no": "Nein",
        "legal_de_authoritative": "Bei rechtlichen Unklarheiten gilt die deutsche Fassung.",
        "download": "Download",
        "delete": "Löschen",
        "flash_delete_not_allowed": "Du darfst dieses Bild nicht löschen.",
        "flash_image_deleted": "Bild gelöscht.",
    },
    "en": {
        "brand": "Svenja & Dominic",
        "subtitle": "Welcome to our wedding app",
        "login_note": "Password-protected access for our guests.",
        "login": "Login",
        "name": "Your name",
        "event_password": "Event password",
        "login_submit": "Open guest area",
        "guest_area": "Guest Area",
        "hello_guest": "Hello {name}.",
        "logout": "Logout",
        "rsvp": "RSVP",
        "upload": "Upload",
        "gallery": "Gallery",
        "host_area": "Host Area",
        "info": "Info",
        "save": "Save",
        "attending": "I will attend",
        "not_attending": "I cannot attend",
        "plus_one": "I will bring a plus-one",
        "file": "Select image",
        "add_more_files": "Add more files",
        "upload_multi_hint": "You can select multiple images at once or add more file fields.",
        "upload_selected_count": "{count} images selected",
        "upload_submit": "Upload photo",
        "schedule": "Schedule",
        "hotels": "Hotels",
        "taxi": "Taxi",
        "location": "Location",
        "visit_location": "Visit location website",
        "privacy": "Privacy",
        "imprint": "Imprint",
        "hero_headline": "Welcome to our wedding",
        "hero_text": "We are so excited to celebrate this special day with you.",
        "to_guest_area": "Open guest area",
        "schedule_text": "3:00 PM ceremony, 5:00 PM reception, 7:00 PM dinner.",
        "hotels_text": "Recommendations will follow. Please book early.",
        "taxi_text": "Taxi service: 01234 / 567890 (24/7).",
        "gallery_uploaded_by": "by {name}",
        "gallery_empty": "No photos available yet.",
        "gallery_image_alt": "Uploaded by {name}",
        "flash_enter_name": "Please enter your name.",
        "flash_invalid_password": "Invalid event password.",
        "flash_rsvp_select": "Please choose an RSVP option.",
        "flash_rsvp_saved": "RSVP saved.",
        "flash_select_image": "Please select an image file.",
        "flash_allowed_types": "Only JPG/JPEG/PNG/HEIC/HEIF are allowed.",
        "flash_upload_success": "Upload successful.",
        "flash_upload_success_count": "{count} images uploaded successfully.",
        "flash_upload_too_large": "Upload too large. Please upload smaller batches (max {max_mb} MB per request).",
        "flash_upload_failed": "Upload failed. Please try again.",
        "flash_invalid_host_password": "Invalid host password.",
        "host_access_title": "Host Area",
        "host_access_note": "This section is intended for the wedding hosts only.",
        "host_password": "Host password",
        "host_access_submit": "Open admin area",
        "host_stats_title": "Overview",
        "total_guests": "Total guests",
        "attending_yes": "Attending",
        "attending_no": "Declined",
        "attending_open": "Pending",
        "plus_one_total": "Plus-ones",
        "host_table_name": "Name",
        "host_table_status": "RSVP",
        "host_table_plus_one": "Plus-one",
        "status_yes": "Attending",
        "status_no": "Not attending",
        "status_open": "Pending",
        "yes": "Yes",
        "no": "No",
        "legal_de_authoritative": "In case of legal ambiguity, the German version shall prevail.",
        "download": "Download",
        "delete": "Delete",
        "flash_delete_not_allowed": "You are not allowed to delete this image.",
        "flash_image_deleted": "Image deleted.",
    },
}


def get_lang() -> str:
    lang = session.get("lang", "de")
    return lang if lang in TEXTS else "de"


def t(key: str) -> str:
    return TEXTS[get_lang()].get(key, key)


def get_hero_image_asset() -> str:
    assets_dir = base_dir / "static" / "assets"
    configured = app.config.get("HERO_IMAGE_FILENAME")

    candidates = []
    if configured:
        candidates.append(configured)
    candidates.extend(["hero.jpg", "hero.jpeg", "hero.png", "image.png", "image-1.png"])

    for filename in candidates:
        if (assets_dir / filename).is_file():
            return f"assets/{filename}"

    return "assets/hero.jpg"


@app.context_processor
def inject_common() -> dict:
    return {
        "t": t,
        "lang": get_lang(),
        "guest_name": session.get("guest_name"),
        "guest_id": session.get("guest_id"),
        "is_host": bool(session.get("is_host")),
        "location_name": app.config["LOCATION_NAME"],
        "location_address": app.config["LOCATION_ADDRESS"],
        "location_website_url": app.config["LOCATION_WEBSITE_URL"],
        "google_maps_embed_url": app.config["GOOGLE_MAPS_EMBED_URL"],
        "wedding_date": app.config["WEDDING_DATE"],
        "hero_image_url": url_for("static", filename=get_hero_image_asset()),
    }


def get_db() -> sqlite3.Connection:
    db_path = app.config["DB_PATH"]
    os.makedirs(os.path.dirname(db_path), exist_ok=True)

    if "db" not in g:
        g.db = sqlite3.connect(db_path)
        g.db.row_factory = sqlite3.Row
    return g.db


@app.teardown_appcontext
def close_db(_error) -> None:
    db = g.pop("db", None)
    if db is not None:
        db.close()


def init_db() -> None:
    db_path = app.config["DB_PATH"]
    db_dir = os.path.dirname(db_path)
    if db_dir:
        os.makedirs(db_dir, exist_ok=True)

    with sqlite3.connect(db_path) as conn:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS guests (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE,
                attending INTEGER,
                plus_one INTEGER NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL
            )
            """
        )
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS uploads (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                filename TEXT NOT NULL,
                uploaded_by INTEGER NOT NULL,
                uploaded_at TEXT NOT NULL,
                FOREIGN KEY(uploaded_by) REFERENCES guests(id)
            )
            """
        )
        conn.commit()


def login_required(view):
    @wraps(view)
    def wrapped(*args, **kwargs):
        if "guest_id" not in session:
            return redirect(url_for("landing"))
        return view(*args, **kwargs)

    return wrapped


@app.errorhandler(RequestEntityTooLarge)
def handle_request_too_large(_error):
    max_mb = max(1, int(app.config.get("MAX_CONTENT_LENGTH", 0)) // (1024 * 1024))
    flash(t("flash_upload_too_large").format(max_mb=max_mb))
    if request.method == "POST":
        return redirect(url_for("upload"))
    return redirect(url_for("landing"))


def is_allowed_file(filename: str) -> bool:
    return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS


def upsert_guest(name: str) -> int:
    now = datetime.utcnow().isoformat()
    db = get_db()
    row = db.execute("SELECT id FROM guests WHERE name = ?", (name,)).fetchone()
    if row:
        return int(row["id"])

    cursor = db.execute(
        "INSERT INTO guests (name, created_at) VALUES (?, ?)",
        (name, now),
    )
    db.commit()
    return int(cursor.lastrowid)


@app.get("/health")
def health():
    return {"status": "ok"}


@app.get("/")
def landing():
    if "guest_id" in session:
        return redirect(url_for("welcome"))
    return render_template("login.html")


@app.post("/login")
def login():
    name = (request.form.get("name") or "").strip()
    event_password = request.form.get("event_password") or ""

    if not name:
        flash(t("flash_enter_name"))
        return redirect(url_for("landing"))

    if event_password != app.config["EVENT_PASSWORD"]:
        flash(t("flash_invalid_password"))
        return redirect(url_for("landing"))

    guest_id = upsert_guest(name)
    session["guest_id"] = guest_id
    session["guest_name"] = name
    session.pop("is_host", None)
    return redirect(url_for("welcome"))


@app.post("/logout")
def logout():
    session.clear()
    return redirect(url_for("landing"))


@app.post("/lang/<code>")
def set_lang(code: str):
    if code in TEXTS:
        session["lang"] = code
    return redirect(request.referrer or url_for("landing"))


@app.get("/welcome")
@login_required
def welcome():
    return render_template("welcome.html")


@app.get("/gaestebereich")
@app.get("/gästebereich")
@login_required
def guest_area():
    return render_template("guest_area.html")


@app.route("/gastgeberbereich", methods=["GET", "POST"])
@login_required
def host_area():
    if request.method == "POST":
        host_password = request.form.get("host_password") or ""
        expected = app.config.get("HOST_PASSWORD", "")
        if not expected or not compare_digest(host_password, expected):
            flash(t("flash_invalid_host_password"))
            return redirect(url_for("host_area"))
        session["is_host"] = True
        return redirect(url_for("host_area"))

    if not session.get("is_host"):
        return render_template("host_area.html", unlocked=False)

    db = get_db()
    stats_row = db.execute(
        """
        SELECT
            COUNT(*) AS total_guests,
            SUM(CASE WHEN attending = 1 THEN 1 ELSE 0 END) AS attending_yes,
            SUM(CASE WHEN attending = 0 THEN 1 ELSE 0 END) AS attending_no,
            SUM(CASE WHEN attending IS NULL THEN 1 ELSE 0 END) AS attending_open,
            SUM(CASE WHEN attending = 1 AND plus_one = 1 THEN 1 ELSE 0 END) AS plus_one_total
        FROM guests
        """
    ).fetchone()
    guests = db.execute(
        """
        SELECT name, attending, plus_one
        FROM guests
        ORDER BY name COLLATE NOCASE ASC
        """
    ).fetchall()

    stats = {
        "total_guests": int(stats_row["total_guests"] or 0),
        "attending_yes": int(stats_row["attending_yes"] or 0),
        "attending_no": int(stats_row["attending_no"] or 0),
        "attending_open": int(stats_row["attending_open"] or 0),
        "plus_one_total": int(stats_row["plus_one_total"] or 0),
    }
    return render_template("host_area.html", unlocked=True, stats=stats, guests=guests)


@app.get("/dashboard")
@login_required
def dashboard():
    return redirect(url_for("guest_area"))


@app.route("/rsvp", methods=["GET", "POST"])
@login_required
def rsvp():
    db = get_db()

    if request.method == "POST":
        attending_raw = request.form.get("attending")
        plus_one = 1 if request.form.get("plus_one") == "on" else 0

        if attending_raw not in {"yes", "no"}:
            flash(t("flash_rsvp_select"))
            return redirect(url_for("rsvp"))

        attending = 1 if attending_raw == "yes" else 0
        if not attending:
            plus_one = 0

        db.execute(
            "UPDATE guests SET attending = ?, plus_one = ? WHERE id = ?",
            (attending, plus_one, session["guest_id"]),
        )
        db.commit()
        flash(t("flash_rsvp_saved"))
        return redirect(url_for("rsvp"))

    guest = db.execute(
        "SELECT attending, plus_one FROM guests WHERE id = ?",
        (session["guest_id"],),
    ).fetchone()

    return render_template("rsvp.html", guest=guest)


@app.route("/upload", methods=["GET", "POST"])
@login_required
def upload():
    if request.method == "POST":
        try:
            files = [f for f in request.files.getlist("photo") if f and f.filename]
            if not files:
                flash(t("flash_select_image"))
                return redirect(url_for("upload"))

            for file in files:
                if not is_allowed_file(file.filename):
                    flash(t("flash_allowed_types"))
                    return redirect(url_for("upload"))
                mime_type = (file.mimetype or "").lower()
                # Some mobile browsers may omit or vary MIME types for valid images.
                if mime_type and mime_type not in ALLOWED_MIME_TYPES:
                    flash(t("flash_allowed_types"))
                    return redirect(url_for("upload"))

            upload_dir = app.config["UPLOAD_FOLDER"]
            os.makedirs(upload_dir, exist_ok=True)
            db = get_db()
            now = datetime.utcnow().isoformat()
            upload_rows = []
            for file in files:
                safe_name = secure_filename(file.filename)
                if "." not in safe_name:
                    flash(t("flash_allowed_types"))
                    return redirect(url_for("upload"))
                ext = safe_name.rsplit(".", 1)[1].lower()
                stored_name = f"{uuid.uuid4().hex}.{ext}"
                file.save(os.path.join(upload_dir, stored_name))
                upload_rows.append((stored_name, session["guest_id"], now))

            db.executemany(
                "INSERT INTO uploads (filename, uploaded_by, uploaded_at) VALUES (?, ?, ?)",
                upload_rows,
            )
            db.commit()

            flash(t("flash_upload_success_count").format(count=len(upload_rows)))
            return redirect(url_for("gallery"))
        except RequestEntityTooLarge:
            raise
        except Exception:
            app.logger.exception("Upload failed")
            flash(t("flash_upload_failed"))
            return redirect(url_for("upload"))

    return render_template("upload.html")


@app.get("/gallery")
@login_required
def gallery():
    db = get_db()
    images = db.execute(
        """
        SELECT uploads.id, uploads.filename, uploads.uploaded_by, uploads.uploaded_at, guests.name AS uploaded_by_name
        FROM uploads
        JOIN guests ON guests.id = uploads.uploaded_by
        ORDER BY uploads.id DESC
        """
    ).fetchall()
    return render_template("gallery.html", images=images)


@app.post("/gallery/delete/<int:image_id>")
@login_required
def delete_image(image_id: int):
    db = get_db()
    image = db.execute(
        "SELECT id, filename, uploaded_by FROM uploads WHERE id = ?",
        (image_id,),
    ).fetchone()
    if image is None:
        return redirect(url_for("gallery"))

    current_guest_id = int(session["guest_id"])
    is_host = bool(session.get("is_host"))
    if not is_host and int(image["uploaded_by"]) != current_guest_id:
        flash(t("flash_delete_not_allowed"))
        return redirect(url_for("gallery"))

    db.execute("DELETE FROM uploads WHERE id = ?", (image_id,))
    db.commit()

    file_path = os.path.join(app.config["UPLOAD_FOLDER"], image["filename"])
    if os.path.isfile(file_path):
        os.remove(file_path)

    flash(t("flash_image_deleted"))
    return redirect(url_for("gallery"))


@app.get("/uploads/<path:filename>")
@login_required
def serve_upload(filename: str):
    as_attachment = request.args.get("download") == "1"
    return send_from_directory(
        app.config["UPLOAD_FOLDER"],
        filename,
        as_attachment=as_attachment,
        download_name=filename if as_attachment else None,
    )


@app.get("/info/<page>")
@login_required
def info(page: str):
    allowed = {"schedule", "hotels", "taxi", "location"}
    if page not in allowed:
        return redirect(url_for("guest_area"))
    return render_template("info.html", page=page)


@app.get("/datenschutz")
def datenschutz():
    return render_template("datenschutz.html")


@app.get("/impressum")
def impressum():
    return render_template("impressum.html")


init_db()

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)
