from datetime import datetime, timedelta, timezone
from typing import Optional, List, Dict, Any, Tuple

from fastapi import APIRouter, Depends, Query
from sqlalchemy import func, and_
from sqlalchemy.orm import Session

# TZ: ZoneInfo ist vorhanden, aber es fehlen auf Windows oft die IANA-Daten.
try:
    from zoneinfo import ZoneInfo
except Exception:  # sehr defensive Fallbacks
    ZoneInfo = None  # type: ignore

from app.core.database import get_db
from app.core.auth import get_current_user, requires_role
from app.models.booking import Booking
from app.models.user import User
from app.models.product import Product
from app.models.delivery import Delivery  # hat KEIN created_at bei dir


router = APIRouter(prefix="/stats", tags=["stats"])


# ----------------- Helpers -----------------

def _get_tz(tz_name: str) -> timezone:
    """
    Liefert eine tzinfo. Auf Windows ohne tzdata fällt das auf UTC zurück,
    statt eine ZoneInfoNotFoundError zu werfen.
    """
    if ZoneInfo is not None:
        try:
            return ZoneInfo(tz_name)  # type: ignore
        except Exception:
            pass
    # Fallback: UTC – korrekt, aber ohne CET/CEST-Offset.
    return timezone.utc


def _to_naive_utc(dt: Optional[datetime]) -> Optional[datetime]:
    """
    Konvertiert timezone-aware nach UTC und entfernt tzinfo.
    Bei None -> None. Bei nativ-naiv wird unverändert zurückgegeben.
    """
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt
    return dt.astimezone(timezone.utc).replace(tzinfo=None)


def _delivery_time_column():
    """
    Ermittelt die Zeitspalte des Delivery-Modells dynamisch.
    Reihenfolge: created_at | timestamp | created | delivered_at
    Gibt (Column, name) oder (None, None) zurück.
    """
    for name in ("created_at", "timestamp", "created", "delivered_at"):
        col = getattr(Delivery, name, None)
        if col is not None:
            return col, name
    return None, None


def _last_delivery_at(db: Session) -> Optional[datetime]:
    """
    Bestimmt den Zeitpunkt der letzten Lieferung anhand der vorhandenen Spalte.
    Achtung: Gibt das zurück, was in der DB liegt (aware oder naiv).
    """
    col, _ = _delivery_time_column()
    if col is None:
        return None
    return db.query(func.max(col)).scalar()


def _period_bounds(db: Session, period: str, tz_name: str = "Europe/Berlin") -> Tuple[Optional[datetime], Optional[datetime], Optional[datetime]]:
    """
    Liefert (from_utc_naive, to_utc_naive, last_delivery_time_original).
    Booking.timestamp ist in deiner App naiv/UTC, daher filtern wir mit naiven UTC-Grenzen.
    """
    tz = _get_tz(tz_name)
    now_local = datetime.now(tz)
    now_utc_naive = now_local.astimezone(timezone.utc).replace(tzinfo=None)

    last_delivery = _last_delivery_at(db)

    if period == "last_delivery":
        if last_delivery is None:
            # Fallback: 30 Tage rückwärts
            from_utc_naive = (now_local - timedelta(days=30)).astimezone(timezone.utc).replace(tzinfo=None)
        else:
            from_utc_naive = _to_naive_utc(last_delivery)
        return (from_utc_naive, now_utc_naive, last_delivery)

    if period == "ytd":
        start_local = datetime(now_local.year, 1, 1, tzinfo=tz)
        start_utc_naive = start_local.astimezone(timezone.utc).replace(tzinfo=None)
        return (start_utc_naive, now_utc_naive, last_delivery)

    # "all" -> keine untere Grenze
    return (None, now_utc_naive, last_delivery)


# ----------------- Öffentliche Stat-Endpunkte (auth-pflichtig, aber nicht nur Admin) -----------------

@router.get("/meta")
def stats_meta(
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
    tz: str = Query("Europe/Berlin")
) -> Dict[str, Any]:
    """
    Metadaten für die Stats-Seite:
    - categories: distinct Product.category (ohne NULL)
    - last_delivery_at: ISO-Zeitpunkt der letzten Lieferung (falls vorhanden)
    - visible_count: Nutzer mit public_stats==true & alias gesetzt & is_active==true
    - hidden_count: aktive Nutzer minus visible_count
    - now: Serverzeit in tz
    """
    # Kategorien
    cats = [
        c for (c,) in db.query(Product.category)
        .filter(Product.category.isnot(None))
        .distinct()
        .order_by(Product.category.asc())
        .all()
    ]

    # Letzte Lieferung – robust gegen fehlende Spalte
    last_delivery_time = _last_delivery_at(db)

    # Sichtbarkeit
    visible_count = (
        db.query(func.count(User.id))
        .filter(
            and_(
                User.is_active.is_(True),
                User.alias.isnot(None),
                func.length(func.trim(User.alias)) > 0,
                getattr(User, "public_stats", False) == True,  # robust falls Migration noch nicht durch
            )
        )
        .scalar()
        or 0
    )
    active_count = db.query(func.count(User.id)).filter(User.is_active.is_(True)).scalar() or 0

    now_local = datetime.now(_get_tz(tz))
    return {
        "categories": cats,
        "last_delivery_at": last_delivery_time.isoformat() if last_delivery_time else None,
        "visible_count": int(visible_count),
        "hidden_count": int(max(0, active_count - visible_count)),
        "now": now_local.isoformat(),
    }


@router.get("/top-drinkers")
def top_drinkers(
    period: str = Query("last_delivery", pattern="^(last_delivery|ytd|all)$"),
    category: str = Query("all"),
    limit: int = Query(5, ge=1, le=50),
    tz: str = Query("Europe/Berlin"),
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
) -> List[Dict[str, Any]]:
    """
    Top-Trinker je Zeitraum und optional Kategorie.
    Aggregation: Summe Booking.amount (Stückzahl), zusätzlich count(*) und SUM(total_cents).
    Nur Nutzer mit Opt-in (public_stats) + Alias + aktiv.
    """
    from_dt, to_dt, _ = _period_bounds(db, period, tz)

    q = (
        db.query(
            User.id.label("user_id"),
            User.alias.label("alias"),
            User.avatar_url.label("avatar_url"),
            func.coalesce(func.sum(Booking.amount), 0).label("amount_sum"),
            func.count(Booking.id).label("count"),
            func.coalesce(func.sum(Booking.total_cents), 0).label("revenue_cents"),
        )
        .join(User, User.id == Booking.user_id)
    )

    if category != "all":
        q = q.join(Product, Product.id == Booking.product_id).filter(Product.category == category)

    if from_dt is not None:
        q = q.filter(Booking.timestamp >= from_dt)
    if to_dt is not None:
        q = q.filter(Booking.timestamp <= to_dt)

    # Sichtbarkeitsregeln – robust falls Spalte noch nicht migriert:
    public_stats_col = getattr(User, "public_stats", None)
    conds = [
        User.is_active.is_(True),
        User.alias.isnot(None),
        func.length(func.trim(User.alias)) > 0,
    ]
    if public_stats_col is not None:
        conds.append(public_stats_col.is_(True))

    q = q.filter(and_(*conds))

    q = q.group_by(User.id, User.alias, User.avatar_url).order_by(func.coalesce(func.sum(Booking.amount), 0).desc()).limit(limit)
    rows = q.all()

    return [
        {
            "user_id": r.user_id,
            "alias": r.alias,
            "avatar_url": r.avatar_url,
            "amount_sum": int(r.amount_sum or 0),
            "count": int(r.count or 0),
            "revenue_cents": int(r.revenue_cents or 0),
        }
        for r in rows
    ]


@router.get("/product-share")
def product_share(
    period: str = Query("last_delivery", pattern="^(last_delivery|ytd|all)$"),
    tz: str = Query("Europe/Berlin"),
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
) -> Dict[str, Any]:
    """
    Produktverteilung (Kreisdiagramm): COUNT(Booking.id) je Produkt im Zeitraum.
    Unabhängig vom Opt-in, da keine personenbezogenen Daten.
    """
    from_dt, to_dt, _ = _period_bounds(db, period, tz)

    q = (
        db.query(
            Product.id.label("product_id"),
            Product.name.label("product_name"),
            func.count(Booking.id).label("count_rows"),
        )
        .join(Product, Product.id == Booking.product_id)
    )

    if from_dt is not None:
        q = q.filter(Booking.timestamp >= from_dt)
    if to_dt is not None:
        q = q.filter(Booking.timestamp <= to_dt)

    q = q.group_by(Product.id, Product.name).order_by(func.count(Booking.id).desc())
    items = q.all()

    total = sum(int(r.count_rows or 0) for r in items)
    return {
        "total": int(total),
        "items": [
            {
                "product_id": r.product_id,
                "product_name": r.product_name,
                "count": int(r.count_rows or 0),
            }
            for r in items
        ],
    }


# ----------------- Bestehende Admin-Stats (weiterhin verfügbar) -----------------

@router.get("/summary", dependencies=[Depends(requires_role("manager", "admin"))])
def summary(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    users = db.query(func.count(User.id)).scalar() or 0
    products = db.query(func.count(Product.id)).scalar() or 0
    bookings = db.query(func.count(Booking.id)).scalar() or 0
    total_revenue = db.query(func.coalesce(func.sum(Booking.total_cents), 0)).scalar() or 0

    return {
        "num_users": users,
        "num_products": products,
        "num_bookings": bookings,
        "total_revenue_cents": int(total_revenue),
        "my_balance_cents": int(user.balance_cents or 0),
        "stock_overview": None,
    }


@router.get("/revenue-summary", dependencies=[Depends(requires_role("manager", "admin"))])
def revenue_summary(db: Session = Depends(get_db), _: User = Depends(get_current_user)):
    total_cents = db.query(func.coalesce(func.sum(Booking.total_cents), 0)).scalar() or 0

    # dynamische Delivery-Zeitspalte verwenden
    last_delivery_time = _last_delivery_at(db)

    if last_delivery_time:
        since_last_cents = (
            db.query(func.coalesce(func.sum(Booking.total_cents), 0))
            .filter(Booking.timestamp > _to_naive_utc(last_delivery_time))
            .scalar()
            or 0
        )
    else:
        since_last_cents = total_cents

    return {
        "revenue_total_cents": int(total_cents),
        "revenue_since_last_delivery_cents": int(since_last_cents),
        "last_delivery_at": last_delivery_time.isoformat() if last_delivery_time else None,
    }


# (optionale Alt-Endpunkte, falls noch genutzt)
@router.get("/consumption-per-user", dependencies=[Depends(requires_role("manager", "admin"))])
def consumption_per_user(db: Session = Depends(get_db), _: User = Depends(get_current_user)):
    rows = (
        db.query(
            User.id.label("user_id"),
            User.name,
            func.coalesce(func.sum(Booking.total_cents), 0).label("total_cents"),
        )
        .outerjoin(Booking, Booking.user_id == User.id)
        .group_by(User.id, User.name)
        .order_by(func.coalesce(func.sum(Booking.total_cents), 0).desc())
        .all()
    )
    return [{"user_id": r.user_id, "name": r.name, "total_cents": int(r.total_cents or 0)} for r in rows]


@router.get("/consumption-per-product", dependencies=[Depends(requires_role("manager", "admin"))])
def consumption_per_product(db: Session = Depends(get_db), _: User = Depends(get_current_user)):
    rows = (
        db.query(
            Product.id.label("product_id"),
            Product.name.label("name"),
            func.count(Booking.id).label("count"),
        )
        .outerjoin(Booking, Booking.product_id == Product.id)
        .group_by(Product.id, Product.name)
        .order_by(func.count(Booking.id).desc())
        .all()
    )
    return [{"product_id": r.product_id, "name": r.name, "count": int(r.count or 0)} for r in rows]


@router.get("/monthly-ranking", dependencies=[Depends(requires_role("manager", "admin"))])
def monthly_ranking(
    year: Optional[int] = Query(None),
    month: Optional[int] = Query(None),
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
):
    q = (
        db.query(
            User.id.label("user_id"),
            User.name,
            func.coalesce(func.sum(Booking.total_cents), 0).label("total_cents"),
        )
        .join(User, User.id == Booking.user_id)
        .group_by(User.id, User.name)
        .order_by(func.coalesce(func.sum(Booking.total_cents), 0).desc())
        .limit(10)
    )
    return [{"user_id": r.user_id, "name": r.name, "total_cents": int(r.total_cents or 0)} for r in q.all()]
