"""Recherche - Kontaktsammlungen für Kampagnen zusammenstellen."""

import csv
import io
import json
from datetime import datetime

from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
from sqlalchemy import and_, or_
from sqlalchemy.orm import Session, joinedload

from dependencies import get_current_user, get_db
from models.aktivitaet import Aktivitaet
from models.benutzer import Benutzer
from models.beziehung import Beziehung
from models.gruppe import Gruppe, KontaktGruppe
from models.kampagne import Kampagne, KampagneKontakt
from models.kontakt import Kontakt
from models.recherche import GespeicherteRecherche
from models.tag import KontaktTag, Tag, TagKategorie

router = APIRouter(prefix="/recherche")

from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")


def _get_multi(params, key: str) -> list[str]:
    """Holt mehrere Werte für einen Query-Parameter (z.B. tag=1&tag=3)."""
    if hasattr(params, "getlist"):
        vals = params.getlist(key)
    else:
        val = params.get(key)
        vals = [val] if val else []
    return [v for v in vals if v]


def _build_query(db: Session, params):
    """Baut die gefilterte Kontakt-Query auf."""
    query = db.query(Kontakt).filter(Kontakt.ist_blacklisted == False)

    # Typ
    typ = params.get("typ")
    if typ and typ in ("person", "organisation"):
        query = query.filter(Kontakt.typ == typ)

    # Gruppen (mehrere möglich, OR-Verknüpfung)
    gruppe_ids = _get_multi(params, "gruppe")
    if gruppe_ids:
        int_ids = [int(g) for g in gruppe_ids]
        query = query.filter(
            Kontakt.id.in_(
                db.query(KontaktGruppe.kontakt_id).filter(KontaktGruppe.gruppe_id.in_(int_ids))
            )
        )

    # Tags (mehrere möglich)
    tag_ids = _get_multi(params, "tag")
    verknuepfung = params.get("tag_logic", "oder")
    if tag_ids:
        int_ids = [int(t) for t in tag_ids]
        if verknuepfung == "und":
            # UND: Kontakt muss ALLE gewählten Tags haben
            for tid in int_ids:
                query = query.filter(
                    Kontakt.id.in_(
                        db.query(KontaktTag.kontakt_id).filter(KontaktTag.tag_id == tid)
                    )
                )
        else:
            # ODER: Kontakt muss mindestens einen der Tags haben
            query = query.filter(
                Kontakt.id.in_(
                    db.query(KontaktTag.kontakt_id).filter(KontaktTag.tag_id.in_(int_ids))
                )
            )

    # Ort
    ort = params.get("ort")
    if ort:
        query = query.filter(Kontakt.ort.ilike(f"%{ort}%"))

    # PLZ-Bereich
    plz = params.get("plz")
    if plz:
        query = query.filter(Kontakt.plz.ilike(f"{plz}%"))

    # Betreuer
    betreuer = params.get("betreuer")
    if betreuer:
        query = query.filter(Kontakt.betreuer == betreuer)

    # Eigentümer
    eigentuemer = params.get("eigentuemer")
    if eigentuemer:
        query = query.filter(Kontakt.eigentuemer == eigentuemer)

    # Hat E-Mail
    hat_email = params.get("hat_email")
    if hat_email == "ja":
        query = query.filter(Kontakt.email1.isnot(None), Kontakt.email1 != "")
    elif hat_email == "nein":
        query = query.filter(or_(Kontakt.email1.is_(None), Kontakt.email1 == ""))

    # Hat Telefon (AND-Logik: kein Telefon UND kein Mobil UND kein Direktwahl)
    hat_telefon = params.get("hat_telefon")
    if hat_telefon == "ja":
        query = query.filter(or_(
            and_(Kontakt.telefon.isnot(None), Kontakt.telefon != ""),
            and_(Kontakt.mobil.isnot(None), Kontakt.mobil != ""),
            and_(Kontakt.telefon_direkt.isnot(None), Kontakt.telefon_direkt != ""),
        ))
    elif hat_telefon == "nein":
        query = query.filter(
            or_(Kontakt.telefon.is_(None), Kontakt.telefon == ""),
            or_(Kontakt.mobil.is_(None), Kontakt.mobil == ""),
            or_(Kontakt.telefon_direkt.is_(None), Kontakt.telefon_direkt == ""),
        )

    # Newsletter
    newsletter = params.get("newsletter")
    if newsletter == "ja":
        query = query.filter(Kontakt.newsletter == True)
    elif newsletter == "nein":
        query = query.filter(Kontakt.newsletter == False)

    # Keine Mails
    keine_mails = params.get("keine_mails")
    if keine_mails == "ja":
        query = query.filter(Kontakt.keine_mails == True)
    elif keine_mails == "nein":
        query = query.filter(Kontakt.keine_mails == False)

    # Keine Briefe
    keine_briefe = params.get("keine_briefe")
    if keine_briefe == "ja":
        query = query.filter(Kontakt.keine_briefe == True)
    elif keine_briefe == "nein":
        query = query.filter(Kontakt.keine_briefe == False)

    # Keine Anrufe
    keine_anrufe = params.get("keine_anrufe")
    if keine_anrufe == "ja":
        query = query.filter(Kontakt.keine_anrufe == True)
    elif keine_anrufe == "nein":
        query = query.filter(Kontakt.keine_anrufe == False)

    # Projekte (Kontakte die Aktivitaeten mit gewaehlten Projekten haben)
    projekt_filter = _get_multi(params, "projekt")
    if projekt_filter:
        query = query.filter(
            Kontakt.id.in_(
                db.query(Aktivitaet.kontakt_id).filter(Aktivitaet.projekt.in_(projekt_filter))
            )
        )

    # Kampagnen-Mitgliedschaft
    r_kampagne = params.get("r_kampagne")
    if r_kampagne:
        query = query.filter(
            Kontakt.id.in_(
                db.query(KampagneKontakt.kontakt_id).filter(
                    KampagneKontakt.kampagne_id == int(r_kampagne)
                )
            )
        )

    # Aktivitäten-Filter (Freitext, Art, Status)
    akt_text = params.get("akt_text")
    akt_art = params.get("akt_art")
    akt_status = params.get("akt_status")
    if akt_text or akt_art or akt_status:
        akt_q = db.query(Aktivitaet.kontakt_id)
        if akt_text:
            like = f"%{akt_text}%"
            akt_q = akt_q.filter(
                or_(Aktivitaet.betreff.ilike(like), Aktivitaet.bemerkung.ilike(like))
            )
        if akt_art:
            akt_q = akt_q.filter(Aktivitaet.art == akt_art)
        if akt_status:
            akt_q = akt_q.filter(Aktivitaet.status == akt_status)
        query = query.filter(Kontakt.id.in_(akt_q))

    # Freitext mit wählbaren Feldern
    q = params.get("q")
    if q:
        like = f"%{q}%"
        felder = _get_multi(params, "q_feld")

        feld_filter = {
            "name": [Kontakt.vorname.ilike(like), Kontakt.nachname.ilike(like)],
            "organisation": [Kontakt.firmenname.ilike(like)],
            "email": [Kontakt.email1.ilike(like), Kontakt.email2.ilike(like)],
            "adresse": [Kontakt.strasse.ilike(like), Kontakt.ort.ilike(like), Kontakt.plz.ilike(like)],
            "telefon": [Kontakt.telefon.ilike(like), Kontakt.mobil.ilike(like), Kontakt.telefon_direkt.ilike(like)],
            "position": [Kontakt.position.ilike(like)],
            "bemerkung": [Kontakt.bemerkung.ilike(like)],
        }

        if felder:
            bedingungen = []
            for feld in felder:
                if feld in feld_filter:
                    bedingungen.extend(feld_filter[feld])
            if bedingungen:
                query = query.filter(or_(*bedingungen))
        else:
            # Kein Feld gewählt → alle durchsuchen
            alle = []
            for filters in feld_filter.values():
                alle.extend(filters)
            query = query.filter(or_(*alle))

    return query.order_by(Kontakt.nachname, Kontakt.vorname, Kontakt.firmenname)


def _hat_filter(params) -> bool:
    """Prüft ob mindestens ein Filter gesetzt ist."""
    for key in ("typ", "gruppe", "tag", "ort", "plz", "betreuer", "eigentuemer",
                "hat_email", "hat_telefon", "q", "newsletter", "keine_mails",
                "keine_briefe", "keine_anrufe", "projekt", "akt_text", "akt_art", "akt_status",
                "r_kampagne"):
        if _get_multi(params, key) if key in ("gruppe", "tag", "projekt") else params.get(key):
            return True
    return False


def _gruppiere_ergebnisse(db: Session, gefundene: list):
    """Gruppiert gefundene Kontakte: Personen unter ihren Organisationen."""
    gefundene_ids = {k.id for k in gefundene}
    personen = [k for k in gefundene if k.typ == "person"]
    orgs = {k.id: k for k in gefundene if k.typ == "organisation"}

    person_ids = [p.id for p in personen]
    person_ids_set = set(person_ids)

    # Beziehungen Person→Org laden
    org_zuordnung = {}  # person_id -> (org_kontakt, rolle)

    if person_ids:
        beziehungen = (
            db.query(Beziehung)
            .filter(
                or_(
                    Beziehung.kontakt_a_id.in_(person_ids),
                    Beziehung.kontakt_b_id.in_(person_ids),
                ),
                Beziehung.ist_aktiv == True,
            )
            .options(
                joinedload(Beziehung.kontakt_a),
                joinedload(Beziehung.kontakt_b),
            )
            .all()
        )

        for bez in beziehungen:
            if bez.kontakt_a_id in person_ids_set and bez.kontakt_b and bez.kontakt_b.typ == "organisation":
                pid = bez.kontakt_a_id
                org = bez.kontakt_b
                rolle = bez.rolle_a or ""
            elif bez.kontakt_b_id in person_ids_set and bez.kontakt_a and bez.kontakt_a.typ == "organisation":
                pid = bez.kontakt_b_id
                org = bez.kontakt_a
                rolle = bez.rolle_b or ""
            else:
                continue
            if pid not in org_zuordnung:
                org_zuordnung[pid] = (org, rolle)

    # Gruppen aufbauen
    org_gruppen_dict = {}  # org_id -> dict
    standalone = []

    for person in personen:
        if person.id in org_zuordnung:
            org, rolle = org_zuordnung[person.id]
            if org.id not in org_gruppen_dict:
                org_gruppen_dict[org.id] = {
                    "org": org,
                    "gefunden": org.id in gefundene_ids,
                    "mitarbeiter": [],
                }
            org_gruppen_dict[org.id]["mitarbeiter"].append({"kontakt": person, "rolle": rolle})
        else:
            standalone.append(person)

    # Gefundene Orgs ohne zugeordnete MA
    for org_id, org in orgs.items():
        if org_id not in org_gruppen_dict:
            org_gruppen_dict[org_id] = {
                "org": org,
                "gefunden": True,
                "mitarbeiter": [],
            }

    # Sortieren
    org_gruppen = sorted(org_gruppen_dict.values(), key=lambda g: (g["org"].firmenname or "").lower())
    for gruppe in org_gruppen:
        gruppe["mitarbeiter"].sort(key=lambda m: (m["kontakt"].nachname or "", m["kontakt"].vorname or ""))
    standalone.sort(key=lambda k: (k.nachname or "", k.vorname or ""))

    return {
        "org_gruppen": org_gruppen,
        "standalone": standalone,
        "gefundene_ids": gefundene_ids,
    }


@router.get("", response_class=HTMLResponse)
async def recherche(
    request: Request,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    gruppen = db.query(Gruppe).order_by(Gruppe.name).all()
    kategorien = db.query(TagKategorie).order_by(TagKategorie.sortierung).all()
    benutzer = db.query(Benutzer).filter(Benutzer.ist_aktiv == True).order_by(Benutzer.name).all()
    projekte = [r[0] for r in db.query(Aktivitaet.projekt).filter(Aktivitaet.projekt.isnot(None)).distinct().order_by(Aktivitaet.projekt).all()]

    params = request.query_params
    ergebnis = None
    gesamt = 0
    hat_filter = _hat_filter(params)

    if hat_filter:
        query = _build_query(db, params)
        gesamt = query.count()
        kontakte = query.limit(1000).all()
        ergebnis = _gruppiere_ergebnisse(db, kontakte)

    # Aktive Filter für Template vorbereiten
    aktive_gruppe_ids = [int(g) for g in _get_multi(params, "gruppe")]
    aktive_tag_ids = [int(t) for t in _get_multi(params, "tag")]
    aktive_projekte = _get_multi(params, "projekt")
    q_felder = _get_multi(params, "q_feld")

    # Kampagnen für "An Kampagne übergeben"
    kampagnen = db.query(Kampagne).order_by(Kampagne.erstellt_am.desc()).all()

    # Gespeicherte Recherchen
    gespeicherte = db.query(GespeicherteRecherche).order_by(GespeicherteRecherche.erstellt_am.desc()).all()

    return templates.TemplateResponse("pages/recherche.html", {
        "request": request,
        "user": user,
        "active_page": "recherche",
        "gruppen": gruppen,
        "kategorien": kategorien,
        "benutzer": benutzer,
        "ergebnis": ergebnis,
        "gesamt": gesamt,
        "hat_filter": hat_filter,
        "params": params,
        "aktive_gruppe_ids": aktive_gruppe_ids,
        "aktive_tag_ids": aktive_tag_ids,
        "aktive_projekte": aktive_projekte,
        "q_felder": q_felder,
        "projekte": projekte,
        "kampagnen": kampagnen,
        "art_labels": Aktivitaet.ART_LABELS,
        "gespeicherte": gespeicherte,
    })


@router.post("/export/csv")
async def export_csv(
    request: Request,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """CSV-Export der ausgewählten Kontakte."""
    form = await request.form()
    kontakt_ids = [int(i) for i in form.getlist("kontakt_ids") if i]
    if not kontakt_ids:
        return RedirectResponse(url="/recherche", status_code=303)

    kontakte = db.query(Kontakt).filter(Kontakt.id.in_(kontakt_ids)).all()

    output = io.StringIO()
    writer = csv.writer(output, delimiter=";")
    writer.writerow([
        "Typ", "Anrede", "Titel", "Vorname", "Nachname", "Firmenname",
        "Position", "Straße", "PLZ", "Ort", "Land",
        "E-Mail", "Telefon", "Mobil",
    ])
    for k in kontakte:
        writer.writerow([
            k.typ, k.anrede or "", k.titel or "", k.vorname or "", k.nachname or "",
            k.firmenname or "", k.position or "", k.strasse or "", k.plz or "",
            k.ort or "", k.land or "", k.email1 or "", k.telefon or "", k.mobil or "",
        ])

    content = output.getvalue()
    return StreamingResponse(
        io.BytesIO(content.encode("utf-8-sig")),
        media_type="text/csv; charset=utf-8",
        headers={"Content-Disposition": "attachment; filename=recherche_export.csv"},
    )


@router.post("/export/emails")
async def export_emails(
    request: Request,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """E-Mail-Liste der ausgewählten Kontakte."""
    form = await request.form()
    kontakt_ids = [int(i) for i in form.getlist("kontakt_ids") if i]
    if not kontakt_ids:
        return RedirectResponse(url="/recherche", status_code=303)

    kontakte = (
        db.query(Kontakt)
        .filter(Kontakt.id.in_(kontakt_ids), Kontakt.email1.isnot(None), Kontakt.email1 != "")
        .all()
    )

    emails = [k.email1 for k in kontakte if k.email1]
    content = "\n".join(emails)

    return StreamingResponse(
        io.BytesIO(content.encode("utf-8")),
        media_type="text/plain; charset=utf-8",
        headers={"Content-Disposition": "attachment; filename=email_liste.txt"},
    )


@router.post("/zu-kampagne")
async def zu_kampagne(
    request: Request,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """Uebergibt ausgewählte Kontakte an eine bestehende oder neue Kampagne."""
    form = await request.form()
    kampagne_id = form.get("kampagne_id")
    neuer_titel = form.get("neuer_titel", "").strip()
    kontakt_ids = [int(i) for i in form.getlist("kontakt_ids") if i]

    # Kampagne bestimmen
    if kampagne_id == "neu" or (not kampagne_id and neuer_titel):
        kampagne = Kampagne(
            titel=neuer_titel or "Neue Kampagne",
            erstellt_von=user.kuerzel,
        )
        db.add(kampagne)
        db.flush()
    else:
        kampagne = db.query(Kampagne).filter(Kampagne.id == int(kampagne_id)).first()
        if not kampagne:
            return RedirectResponse(url="/recherche", status_code=303)

    # Kontakte laden und hinzufügen
    kontakte = db.query(Kontakt).filter(Kontakt.id.in_(kontakt_ids)).all() if kontakt_ids else []
    bestehende = {kk.kontakt_id for kk in kampagne.kontakte}

    for kontakt in kontakte:
        if kontakt.id not in bestehende:
            db.add(KampagneKontakt(
                kampagne_id=kampagne.id,
                kontakt_id=kontakt.id,
                hinzugefuegt_von=user.kuerzel,
            ))

    db.commit()
    return RedirectResponse(url=f"/kampagnen/{kampagne.id}", status_code=303)


# --- Gespeicherte Recherchen ---


@router.post("/speichern")
async def recherche_speichern(
    request: Request,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """Speichert die aktuelle Selektion als benannte Recherche."""
    form = await request.form()
    name = form.get("name", "").strip()
    kontakt_ids = [int(i) for i in form.getlist("kontakt_ids") if i]

    if not name or not kontakt_ids:
        return RedirectResponse(url="/recherche", status_code=303)

    recherche = GespeicherteRecherche(
        name=name,
        kontakt_ids_json=json.dumps(kontakt_ids),
        erstellt_von=user.kuerzel,
    )
    db.add(recherche)
    db.commit()

    return RedirectResponse(url=f"/recherche/gespeichert/{recherche.id}", status_code=303)


@router.get("/gespeichert", response_class=HTMLResponse)
async def gespeicherte_liste(
    request: Request,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """Liste aller gespeicherten Recherchen."""
    recherchen = db.query(GespeicherteRecherche).order_by(GespeicherteRecherche.erstellt_am.desc()).all()
    return templates.TemplateResponse("pages/recherche_gespeichert.html", {
        "request": request,
        "user": user,
        "active_page": "recherche",
        "recherchen": recherchen,
    })


@router.get("/gespeichert/{recherche_id}", response_class=HTMLResponse)
async def gespeicherte_detail(
    recherche_id: int,
    request: Request,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """Lädt eine gespeicherte Recherche und zeigt die Kontakte."""
    recherche = db.query(GespeicherteRecherche).filter(GespeicherteRecherche.id == recherche_id).first()
    if not recherche:
        return RedirectResponse(url="/recherche", status_code=303)

    kontakt_ids = json.loads(recherche.kontakt_ids_json)
    kontakte = db.query(Kontakt).filter(Kontakt.id.in_(kontakt_ids)).all() if kontakt_ids else []
    ergebnis = _gruppiere_ergebnisse(db, kontakte)

    kampagnen = db.query(Kampagne).order_by(Kampagne.erstellt_am.desc()).all()

    return templates.TemplateResponse("pages/recherche_gespeichert_detail.html", {
        "request": request,
        "user": user,
        "active_page": "recherche",
        "recherche": recherche,
        "ergebnis": ergebnis,
        "gesamt": len(kontakt_ids),
        "kampagnen": kampagnen,
        "art_labels": Aktivitaet.ART_LABELS,
        "benutzer": db.query(Benutzer).filter(Benutzer.ist_aktiv == True).order_by(Benutzer.name).all(),
    })


@router.post("/gespeichert/{recherche_id}/loeschen")
async def gespeicherte_loeschen(
    recherche_id: int,
    user=Depends(get_current_user),
    db: Session = Depends(get_db),
):
    """Löscht eine gespeicherte Recherche."""
    recherche = db.query(GespeicherteRecherche).filter(GespeicherteRecherche.id == recherche_id).first()
    if recherche:
        db.delete(recherche)
        db.commit()
    return RedirectResponse(url="/recherche/gespeichert", status_code=303)
