"""
Telemetry KPIs (4A.2).

Admin-only endpoint to compute funnel KPIs from telemetry_events.
"""

from __future__ import annotations

import json
import statistics
from datetime import datetime, timedelta
from typing import Any, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import and_, case, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db
from app.models.telemetry import TelemetryEvent
from app.models.user import User
from app.schemas.referrals import ReferralKpiWindow, ReferralKpisResponse, ReferralReferrerRow
from app.schemas.telemetry import (
    DealFunnelKpis,
    TelemetryKpiWindow,
    TelemetryKpisResponse,
    YieldFunnelKpis,
)


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


def _require_admin(user: User) -> None:
    if not user.is_admin:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")


def _safe_json(metadata_json: Optional[str]) -> dict[str, Any]:
    if not metadata_json:
        return {}
    try:
        value = json.loads(metadata_json)
        return value if isinstance(value, dict) else {}
    except Exception:
        return {}


def _median(values: list[float]) -> Optional[float]:
    if not values:
        return None
    return float(statistics.median(values))


@router.get("/kpis", response_model=TelemetryKpisResponse)
async def get_kpis(
    days: int = Query(30, ge=1, le=365),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    _require_admin(current_user)

    end = datetime.utcnow()
    start = end - timedelta(days=days)

    event_names = [
        # Deal funnel
        "listing_view",
        "inquiry_created",
        "inquiry_status_changed",
        "message_sent",
        "listing_marked_sold",
        # Yield funnel
        "yield_connected",
        "yield_click",
        "yield_conversion",
        "payout_paid",
    ]

    rows = (
        await db.execute(
            select(
                TelemetryEvent.event_name,
                TelemetryEvent.created_at,
                TelemetryEvent.listing_id,
                TelemetryEvent.inquiry_id,
                TelemetryEvent.yield_domain_id,
                TelemetryEvent.click_id,
                TelemetryEvent.metadata_json,
            ).where(
                and_(
                    TelemetryEvent.created_at >= start,
                    TelemetryEvent.created_at <= end,
                    TelemetryEvent.event_name.in_(event_names),
                )
            )
        )
    ).all()

    # -----------------------------
    # Deal KPIs
    # -----------------------------
    listing_views = 0
    inquiries_created = 0
    inquiry_created_at: dict[int, datetime] = {}
    first_seller_reply_at: dict[int, datetime] = {}
    listings_with_inquiries: set[int] = set()
    sold_listings: set[int] = set()
    sold_at_by_listing: dict[int, datetime] = {}
    first_inquiry_at_by_listing: dict[int, datetime] = {}

    # -----------------------------
    # Yield KPIs
    # -----------------------------
    connected_domains = 0
    clicks = 0
    conversions = 0
    payouts_paid = 0
    payouts_paid_amount_total = 0.0

    for event_name, created_at, listing_id, inquiry_id, yield_domain_id, click_id, metadata_json in rows:
        created_at = created_at  # already datetime

        if event_name == "listing_view":
            listing_views += 1
            continue

        if event_name == "inquiry_created":
            inquiries_created += 1
            if inquiry_id:
                inquiry_created_at[inquiry_id] = created_at
            if listing_id:
                listings_with_inquiries.add(listing_id)
                prev = first_inquiry_at_by_listing.get(listing_id)
                if prev is None or created_at < prev:
                    first_inquiry_at_by_listing[listing_id] = created_at
            continue

        if event_name == "message_sent":
            if not inquiry_id:
                continue
            meta = _safe_json(metadata_json)
            if meta.get("role") == "seller":
                prev = first_seller_reply_at.get(inquiry_id)
                if prev is None or created_at < prev:
                    first_seller_reply_at[inquiry_id] = created_at
            continue

        if event_name == "listing_marked_sold":
            if listing_id:
                sold_listings.add(listing_id)
                sold_at_by_listing[listing_id] = created_at
            continue

        if event_name == "yield_connected":
            connected_domains += 1
            continue

        if event_name == "yield_click":
            clicks += 1
            continue

        if event_name == "yield_conversion":
            conversions += 1
            continue

        if event_name == "payout_paid":
            payouts_paid += 1
            meta = _safe_json(metadata_json)
            amount = meta.get("amount")
            if isinstance(amount, (int, float)):
                payouts_paid_amount_total += float(amount)
            continue

    seller_replied_inquiries = len(first_seller_reply_at.keys())
    inquiry_reply_rate = (seller_replied_inquiries / inquiries_created) if inquiries_created else 0.0

    # Inquiry → Sold rate (on listing-level intersection)
    sold_from_inquiry = sold_listings.intersection(listings_with_inquiries)
    inquiry_to_sold_listing_rate = (len(sold_from_inquiry) / len(listings_with_inquiries)) if listings_with_inquiries else 0.0

    # Median reply time (seconds): inquiry_created → first seller message
    reply_deltas: list[float] = []
    for inq_id, created in inquiry_created_at.items():
        replied = first_seller_reply_at.get(inq_id)
        if replied:
            reply_deltas.append((replied - created).total_seconds())

    # Median time-to-sold (seconds): first inquiry on listing → listing sold
    sold_deltas: list[float] = []
    for listing in sold_from_inquiry:
        inq_at = first_inquiry_at_by_listing.get(listing)
        sold_at = sold_at_by_listing.get(listing)
        if inq_at and sold_at and sold_at >= inq_at:
            sold_deltas.append((sold_at - inq_at).total_seconds())

    deal = DealFunnelKpis(
        listing_views=listing_views,
        inquiries_created=inquiries_created,
        seller_replied_inquiries=seller_replied_inquiries,
        inquiry_reply_rate=float(inquiry_reply_rate),
        listings_with_inquiries=len(listings_with_inquiries),
        listings_sold=len(sold_listings),
        inquiry_to_sold_listing_rate=float(inquiry_to_sold_listing_rate),
        median_reply_seconds=_median(reply_deltas),
        median_time_to_sold_seconds=_median(sold_deltas),
    )

    yield_kpis = YieldFunnelKpis(
        connected_domains=connected_domains,
        clicks=clicks,
        conversions=conversions,
        conversion_rate=float(conversions / clicks) if clicks else 0.0,
        payouts_paid=payouts_paid,
        payouts_paid_amount_total=float(payouts_paid_amount_total),
    )

    return TelemetryKpisResponse(
        window=TelemetryKpiWindow(days=days, start=start, end=end),
        deal=deal,
        yield_=yield_kpis,
    )


@router.get("/referrals", response_model=ReferralKpisResponse)
async def get_referral_kpis(
    days: int = Query(30, ge=1, le=365),
    limit: int = Query(200, ge=1, le=1000),
    offset: int = Query(0, ge=0),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    Admin-only referral KPIs for the viral loop (3C.2).

    This is intentionally user-based (users.referred_by_user_id) + telemetry-based (referral_link_viewed),
    so it stays robust even if ref codes evolve.
    """
    _require_admin(current_user)

    end = datetime.utcnow()
    start = end - timedelta(days=days)

    # Referred user counts per referrer (all-time + window)
    referred_counts_subq = (
        select(
            User.referred_by_user_id.label("referrer_user_id"),
            func.count(User.id).label("referred_users_total"),
            func.coalesce(
                func.sum(case((User.created_at >= start, 1), else_=0)),
                0,
            ).label("referred_users_window"),
        )
        .where(User.referred_by_user_id.isnot(None))
        .group_by(User.referred_by_user_id)
        .subquery()
    )

    # Referral link views in window (telemetry)
    link_views_subq = (
        select(
            TelemetryEvent.user_id.label("referrer_user_id"),
            func.count(TelemetryEvent.id).label("referral_link_views_window"),
        )
        .where(
            and_(
                TelemetryEvent.event_name == "referral_link_viewed",
                TelemetryEvent.created_at >= start,
                TelemetryEvent.created_at <= end,
                TelemetryEvent.user_id.isnot(None),
            )
        )
        .group_by(TelemetryEvent.user_id)
        .subquery()
    )

    # Referrers: anyone with an invite_code (we still show even if counts are zero)
    rows = (
        await db.execute(
            select(
                User.id,
                User.email,
                User.invite_code,
                User.created_at,
                func.coalesce(referred_counts_subq.c.referred_users_total, 0),
                func.coalesce(referred_counts_subq.c.referred_users_window, 0),
                func.coalesce(link_views_subq.c.referral_link_views_window, 0),
            )
            .where(User.invite_code.isnot(None))
            .outerjoin(referred_counts_subq, referred_counts_subq.c.referrer_user_id == User.id)
            .outerjoin(link_views_subq, link_views_subq.c.referrer_user_id == User.id)
            .order_by(
                func.coalesce(referred_counts_subq.c.referred_users_window, 0).desc(),
                func.coalesce(referred_counts_subq.c.referred_users_total, 0).desc(),
                User.created_at.desc(),
            )
            .offset(offset)
            .limit(limit)
        )
    ).all()

    referrers = [
        ReferralReferrerRow(
            user_id=int(user_id),
            email=str(email),
            invite_code=str(invite_code) if invite_code else None,
            created_at=created_at,
            referred_users_total=int(referred_total or 0),
            referred_users_window=int(referred_window or 0),
            referral_link_views_window=int(link_views or 0),
        )
        for user_id, email, invite_code, created_at, referred_total, referred_window, link_views in rows
    ]

    totals = {}
    totals["referrers_with_invite_code"] = int(
        (
            await db.execute(
                select(func.count(User.id)).where(User.invite_code.isnot(None))
            )
        ).scalar()
        or 0
    )
    totals["referred_users_total"] = int(
        (
            await db.execute(
                select(func.count(User.id)).where(User.referred_by_user_id.isnot(None))
            )
        ).scalar()
        or 0
    )
    totals["referred_users_window"] = int(
        (
            await db.execute(
                select(func.count(User.id)).where(
                    and_(
                        User.referred_by_user_id.isnot(None),
                        User.created_at >= start,
                        User.created_at <= end,
                    )
                )
            )
        ).scalar()
        or 0
    )
    totals["referral_link_views_window"] = int(
        (
            await db.execute(
                select(func.count(TelemetryEvent.id)).where(
                    and_(
                        TelemetryEvent.event_name == "referral_link_viewed",
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                    )
                )
            )
        ).scalar()
        or 0
    )

    return ReferralKpisResponse(
        window=ReferralKpiWindow(days=days, start=start, end=end),
        totals=totals,
        referrers=referrers,
    )

