#!/usr/bin/env python3
"""
Convert 'Mitglieder LvS 25.xlsx' into CSV files suitable for ZIP bundle import
into the Mitgliederverwaltung Nextcloud plugin.

Usage:
    python3 convert_xlsx.py "/path/to/Mitglieder LvS 25.xlsx" [output_dir]

Output (in output_dir, default ./import_bundle):
    Stufen.csv, Familien.csv, Mitglieder.csv, Adressen.csv,
    Telefonnummern.csv, E-Mails.csv, import_bundle.zip
"""

import csv
import os
import re
import sys
import zipfile
from collections import defaultdict
from datetime import datetime

import openpyxl

# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
STUFEN_MAP = {
    "W": "Wölflinge",
    "P": "Pfadfinder",
    "R": "Rover",
}

STUFEN_META = {
    "Wölflinge": {"sort_order": 1, "age_min": 7, "age_max": 10, "color": "#ff9800"},
    "Pfadfinder": {"sort_order": 2, "age_min": 11, "age_max": 16, "color": "#4caf50"},
    "Rover":      {"sort_order": 3, "age_min": 16, "age_max": 25, "color": "#2196f3"},
}

CSV_DELIMITER = ";"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def fmt_date(val):
    """Convert an Excel date value to YYYY-MM-DD string."""
    if val is None:
        return ""
    if isinstance(val, datetime):
        return val.strftime("%Y-%m-%d")
    s = str(val).strip()
    if not s:
        return ""
    for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d", "%d.%m.%Y", "%d/%m/%Y"):
        try:
            return datetime.strptime(s, fmt).strftime("%Y-%m-%d")
        except ValueError:
            continue
    return s


def parse_address(raw):
    """
    Parse 'Straße Nr, PLZ Ort' into (strasse, plz, ort).
    Handles incomplete addresses gracefully.
    """
    if not raw or str(raw).strip().lower() == "none":
        return "", "", ""
    raw = str(raw).strip()
    # Try: "Street, PLZ City"
    m = re.match(r"^(.+?),\s*(\d{5})\s+(.+)$", raw)
    if m:
        return m.group(1).strip(), m.group(2), m.group(3).strip()
    # Try: "Street, City" (no PLZ)
    m = re.match(r"^(.+?),\s*(.+)$", raw)
    if m:
        return m.group(1).strip(), "", m.group(2).strip()
    return raw, "", ""


def clean(val):
    """Stringify a cell value, stripping None."""
    if val is None:
        return ""
    return str(val).strip()


def write_csv(path, headers, rows):
    """Write a semicolon-delimited UTF-8 CSV with BOM."""
    with open(path, "w", newline="", encoding="utf-8-sig") as f:
        w = csv.writer(f, delimiter=CSV_DELIMITER)
        w.writerow(headers)
        w.writerows(rows)
    print(f"  {os.path.basename(path)}: {len(rows)} rows")


# ---------------------------------------------------------------------------
# Readers
# ---------------------------------------------------------------------------
def read_mitglieder_stamm(ws):
    """
    Parse the 'Mitglieder Stamm' sheet.
    Returns list of dicts with keys matching column semantics.
    """
    members = []
    for row in ws.iter_rows(min_row=3, max_row=ws.max_row, values_only=True):
        nachname = clean(row[1])
        if not nachname:
            continue
        members.append({
            "nachname": nachname,
            "vorname": clean(row[2]),
            "geburtsdatum": fmt_date(row[3]),
            "stufe_code": clean(row[5]),          # W / P / R
            "adresse_raw": clean(row[6]),
            "email": clean(row[7]),
            "telefon": clean(row[8]),
            "aktiv": clean(row[9]).lower() == "x",
            "beitrag": clean(row[10]),
        })
    return members


def read_kontaktdaten(ws):
    """
    Parse a 'Kontaktdaten …' sheet.
    Returns dict keyed by (nachname, vorname) → health info dict.
    """
    info = {}
    # Find header row (contains "Name")
    header_row = None
    for r in range(1, min(10, ws.max_row + 1)):
        cell = ws.cell(r, 2).value
        if cell and str(cell).strip().lower() == "name":
            header_row = r
            break
    if header_row is None:
        return info

    for row in ws.iter_rows(min_row=header_row + 1, max_row=ws.max_row, values_only=True):
        nachname = clean(row[1])
        vorname = clean(row[2])
        if not nachname:
            continue
        allergien = clean(row[6])
        if allergien.upper() == "NEIN":
            allergien = ""
        krankheiten = clean(row[5])
        if krankheiten.upper() == "NEIN":
            krankheiten = ""
        medis = clean(row[7])
        if medis.upper() == "NEIN":
            medis = ""

        notes_parts = []
        schwimmer = clean(row[4])
        if schwimmer.upper() == "JA":
            notes_parts.append("Schwimmer: Ja")
        elif schwimmer:
            notes_parts.append(f"Schwimmer: {schwimmer}")
        if krankheiten:
            notes_parts.append(f"Krankheiten: {krankheiten}")
        if medis:
            notes_parts.append(f"Medikamente: {medis}")

        # Parent contact from right side of sheet (cols K-N, indices 11-13)
        parent_name = ""
        parent_phone = ""
        if len(row) > 12:
            parent_name = clean(row[12])  # col M: parent Vorname
        if len(row) > 13:
            parent_phone = clean(row[13])  # col N: parent Mobil

        info[(nachname, vorname)] = {
            "allergien": allergien,
            "notizen": "; ".join(notes_parts) if notes_parts else "",
            "parent_nachname": clean(row[11]) if len(row) > 11 else "",
            "parent_vorname": parent_name,
            "parent_phone": parent_phone,
        }
    return info


def read_kontoliste(ws):
    """
    Parse the 'Kontoliste' sheet.
    Returns list of dicts: kontoinhaber, betrag, iban.
    """
    accounts = []
    for row in ws.iter_rows(min_row=2, max_row=ws.max_row, values_only=True):
        inhaber = clean(row[1])
        if not inhaber:
            continue
        accounts.append({
            "kontoinhaber": inhaber,
            "betrag": clean(row[2]),
            "iban": clean(row[3]),
        })
    return accounts


# ---------------------------------------------------------------------------
# Family grouping
# ---------------------------------------------------------------------------
def build_families(members, accounts):
    """
    Group members into families by shared last name + address.
    Match to Kontoliste by last name.
    Returns:
      - families: list of {name, kontoinhaber, iban}
      - member→family_name mapping
    """
    # Group by (nachname, address) to handle same last name at different addresses
    groups = defaultdict(list)
    for m in members:
        key = m["nachname"]
        groups[key].append(m)

    # Build account lookup: last name → account
    acct_by_name = {}
    for a in accounts:
        # Kontoinhaber is "Nachname Vorname" — extract last name
        parts = a["kontoinhaber"].replace(",", " ").split()
        if parts:
            acct_by_name[parts[0].lower()] = a

    families = []
    member_family = {}  # (nachname, vorname) → family name

    for nachname, group in sorted(groups.items()):
        family_name = f"Familie {nachname}"
        acct = acct_by_name.get(nachname.lower(), {})
        families.append({
            "name": family_name,
            "kontoinhaber": acct.get("kontoinhaber", ""),
            "iban": acct.get("iban", ""),
        })
        for m in group:
            member_family[(m["nachname"], m["vorname"])] = family_name

    return families, member_family


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    xlsx_path = sys.argv[1]
    out_dir = sys.argv[2] if len(sys.argv) > 2 else os.path.join(os.path.dirname(xlsx_path), "import_bundle")
    os.makedirs(out_dir, exist_ok=True)

    print(f"Reading {xlsx_path} ...")
    wb = openpyxl.load_workbook(xlsx_path, data_only=True)

    # --- Read source sheets ---
    members = read_mitglieder_stamm(wb["Mitglieder Stamm"])
    print(f"  {len(members)} members from 'Mitglieder Stamm'")

    kontakt_info = {}
    for sheet_name in wb.sheetnames:
        if sheet_name.startswith("Kontaktdaten"):
            ki = read_kontaktdaten(wb[sheet_name])
            kontakt_info.update(ki)
            print(f"  {len(ki)} entries from '{sheet_name}'")

    accounts = read_kontoliste(wb["Kontoliste"])
    print(f"  {len(accounts)} accounts from 'Kontoliste'")

    families, member_family = build_families(members, accounts)
    print(f"  {len(families)} families grouped")

    print(f"\nWriting CSVs to {out_dir} ...")

    # --- Stufen.csv ---
    stufen_rows = []
    for code, name in sorted(STUFEN_MAP.items(), key=lambda x: STUFEN_META[x[1]]["sort_order"]):
        meta = STUFEN_META[name]
        stufen_rows.append([
            name, meta["sort_order"], meta["age_min"], meta["age_max"], meta["color"]
        ])
    write_csv(
        os.path.join(out_dir, "Stufen.csv"),
        ["Name", "Sortierung", "Mindestalter", "Hoechstalter", "Farbe"],
        stufen_rows,
    )

    # --- Familien.csv ---
    fam_rows = []
    for f in families:
        fam_rows.append([f["name"], f["kontoinhaber"], f["iban"]])
    write_csv(
        os.path.join(out_dir, "Familien.csv"),
        ["Name", "Kontoinhaber", "IBAN"],
        fam_rows,
    )

    # --- Mitglieder.csv ---
    mitglieder_rows = []
    for m in members:
        key = (m["nachname"], m["vorname"])
        ki = kontakt_info.get(key, {})
        stufe_name = STUFEN_MAP.get(m["stufe_code"], "")
        status = "aktiv" if m["aktiv"] else "inaktiv"
        family_name = member_family.get(key, "")

        mitglieder_rows.append([
            m["vorname"],
            m["nachname"],
            m["geburtsdatum"],
            "",  # Geschlecht — not in source
            "mitglied",  # Rolle
            stufe_name,
            "2025-01-01",  # Eintritt — not in source, use reasonable default
            "",  # Austritt
            status,
            ki.get("allergien", ""),
            ki.get("notizen", ""),
            "",  # Zusaetzliche Notizen
            "",  # KV-Typ
            "",  # KV-Name
            family_name,
            m["beitrag"],  # Eingefrorener Beitragssatz
        ])
    write_csv(
        os.path.join(out_dir, "Mitglieder.csv"),
        [
            "Vorname", "Nachname", "Geburtsdatum", "Geschlecht", "Rolle",
            "Stufenname", "Eintritt", "Austritt", "Status", "Allergien",
            "Notizen", "Zusaetzliche Notizen", "KV-Typ", "KV-Name",
            "Familienname", "Eingefrorener Beitragssatz",
        ],
        mitglieder_rows,
    )

    # --- Adressen.csv ---
    addr_rows = []
    for m in members:
        strasse, plz, ort = parse_address(m["adresse_raw"])
        if not strasse:
            continue
        mitgliedername = f"{m['vorname']} {m['nachname']}"
        addr_rows.append([
            mitgliedername, "Hauptadresse", strasse, plz, ort, "Deutschland", "Ja"
        ])
    write_csv(
        os.path.join(out_dir, "Adressen.csv"),
        ["Mitgliedername", "Label", "Strasse", "PLZ", "Ort", "Land", "Primaer"],
        addr_rows,
    )

    # --- Telefonnummern.csv ---
    phone_rows = []
    for m in members:
        # Member's own phone
        if m["telefon"]:
            mitgliedername = f"{m['vorname']} {m['nachname']}"
            phone_rows.append([mitgliedername, "Mobil", m["telefon"]])

    # Parent phones from Kontaktdaten sheets
    seen_parent_phones = set()
    for (nachname, vorname), ki in kontakt_info.items():
        parent_phone = ki.get("parent_phone", "")
        if not parent_phone:
            continue
        mitgliedername = f"{vorname} {nachname}"
        # Split "number oder number" into separate entries
        phones = [p.strip() for p in parent_phone.split("oder")]
        for i, phone in enumerate(phones):
            if not phone:
                continue
            dedup_key = (mitgliedername, phone)
            if dedup_key in seen_parent_phones:
                continue
            seen_parent_phones.add(dedup_key)
            label = f"Eltern {i+1}" if len(phones) > 1 else "Eltern"
            phone_rows.append([mitgliedername, label, phone])

    write_csv(
        os.path.join(out_dir, "Telefonnummern.csv"),
        ["Mitgliedername", "Label", "Nummer"],
        phone_rows,
    )

    # --- E-Mails.csv ---
    email_rows = []
    for m in members:
        if m["email"]:
            mitgliedername = f"{m['vorname']} {m['nachname']}"
            email_rows.append([mitgliedername, "Privat", m["email"]])
    write_csv(
        os.path.join(out_dir, "E-Mails.csv"),
        ["Mitgliedername", "Label", "E-Mail-Adresse"],
        email_rows,
    )

    # --- Create ZIP bundle ---
    zip_path = os.path.join(out_dir, "import_bundle.zip")
    csv_files = [
        "Stufen.csv", "Familien.csv", "Mitglieder.csv",
        "Adressen.csv", "Telefonnummern.csv", "E-Mails.csv",
    ]
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for name in csv_files:
            fpath = os.path.join(out_dir, name)
            if os.path.exists(fpath):
                zf.write(fpath, name)
    print(f"\n  import_bundle.zip created ({len(csv_files)} files)")

    print(f"\nDone! Import via: Mitgliederverwaltung → Import → ZIP-Bundle hochladen")
    print(f"  {zip_path}")


if __name__ == "__main__":
    main()
