"""
Business KPIs exported as Prometheus metrics (4B Ops).

These KPIs are derived from real telemetry events in the database.
We cache computations to avoid putting load on the DB on every scrape.
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any, Optional

from sqlalchemy import and_, func, select

from app.config import get_settings
from app.database import AsyncSessionLocal
from app.models.telemetry import TelemetryEvent


settings = get_settings()

try:
    from prometheus_client import Gauge
except Exception:  # pragma: no cover
    Gauge = None  # type: ignore


@dataclass(frozen=True)
class TelemetryWindowKpis:
    window_days: int
    start: datetime
    end: datetime

    # Deal
    listing_views: int
    inquiries_created: int
    seller_replied_inquiries: int
    inquiry_reply_rate: float
    listings_with_inquiries: int
    listings_sold: int
    inquiry_to_sold_listing_rate: float

    # Yield
    connected_domains: int
    clicks: int
    conversions: int
    conversion_rate: float
    payouts_paid: int
    payouts_paid_amount_total: float


_cache_until_by_days: dict[int, datetime] = {}
_cache_value_by_days: dict[int, TelemetryWindowKpis] = {}


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 {}


async def _compute_window_kpis(days: int) -> TelemetryWindowKpis:
    end = datetime.utcnow()
    start = end - timedelta(days=days)

    async with AsyncSessionLocal() as db:
        # Fast path: grouped counts for pure counter events
        count_events = [
            "listing_view",
            "inquiry_created",
            "yield_connected",
            "yield_click",
            "yield_conversion",
            "payout_paid",
        ]
        grouped = (
            await db.execute(
                select(TelemetryEvent.event_name, func.count(TelemetryEvent.id))
                .where(
                    and_(
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                        TelemetryEvent.event_name.in_(count_events),
                    )
                )
                .group_by(TelemetryEvent.event_name)
            )
        ).all()
        counts = {name: int(cnt) for name, cnt in grouped}

        listing_views = counts.get("listing_view", 0)
        inquiries_created = counts.get("inquiry_created", 0)
        connected_domains = counts.get("yield_connected", 0)
        clicks = counts.get("yield_click", 0)
        conversions = counts.get("yield_conversion", 0)
        payouts_paid = counts.get("payout_paid", 0)

        # Distinct listing counts (deal)
        listings_with_inquiries = (
            await db.execute(
                select(func.count(func.distinct(TelemetryEvent.listing_id))).where(
                    and_(
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                        TelemetryEvent.event_name == "inquiry_created",
                        TelemetryEvent.listing_id.isnot(None),
                    )
                )
            )
        ).scalar() or 0

        listings_sold = (
            await db.execute(
                select(func.count(func.distinct(TelemetryEvent.listing_id))).where(
                    and_(
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                        TelemetryEvent.event_name == "listing_marked_sold",
                        TelemetryEvent.listing_id.isnot(None),
                    )
                )
            )
        ).scalar() or 0

        # For rates we need intersections/uniques; keep it exact via minimal event fetch
        inquiry_listing_ids = (
            await db.execute(
                select(func.distinct(TelemetryEvent.listing_id)).where(
                    and_(
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                        TelemetryEvent.event_name == "inquiry_created",
                        TelemetryEvent.listing_id.isnot(None),
                    )
                )
            )
        ).scalars().all()
        sold_listing_ids = (
            await db.execute(
                select(func.distinct(TelemetryEvent.listing_id)).where(
                    and_(
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                        TelemetryEvent.event_name == "listing_marked_sold",
                        TelemetryEvent.listing_id.isnot(None),
                    )
                )
            )
        ).scalars().all()

        inquiry_set = {int(x) for x in inquiry_listing_ids if x is not None}
        sold_set = {int(x) for x in sold_listing_ids if x is not None}
        sold_from_inquiry = inquiry_set.intersection(sold_set)
        inquiry_to_sold_listing_rate = (len(sold_from_inquiry) / len(inquiry_set)) if inquiry_set else 0.0

        # Seller reply rate: unique inquiries with at least one seller message
        msg_rows = (
            await db.execute(
                select(TelemetryEvent.inquiry_id, TelemetryEvent.metadata_json).where(
                    and_(
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                        TelemetryEvent.event_name == "message_sent",
                        TelemetryEvent.inquiry_id.isnot(None),
                    )
                )
            )
        ).all()
        seller_replied_inquiries_set: set[int] = set()
        for inquiry_id, metadata_json in msg_rows:
            if inquiry_id is None:
                continue
            meta = _safe_json(metadata_json)
            if meta.get("role") == "seller":
                seller_replied_inquiries_set.add(int(inquiry_id))

        seller_replied_inquiries = len(seller_replied_inquiries_set)
        inquiry_reply_rate = (seller_replied_inquiries / inquiries_created) if inquiries_created else 0.0

        # Payout amounts (sum of metadata amounts)
        payout_rows = (
            await db.execute(
                select(TelemetryEvent.metadata_json).where(
                    and_(
                        TelemetryEvent.created_at >= start,
                        TelemetryEvent.created_at <= end,
                        TelemetryEvent.event_name == "payout_paid",
                        TelemetryEvent.metadata_json.isnot(None),
                    )
                )
            )
        ).scalars().all()
        payouts_paid_amount_total = 0.0
        for metadata_json in payout_rows:
            meta = _safe_json(metadata_json)
            amount = meta.get("amount")
            if isinstance(amount, (int, float)):
                payouts_paid_amount_total += float(amount)

        conversion_rate = (conversions / clicks) if clicks else 0.0

        return TelemetryWindowKpis(
            window_days=days,
            start=start,
            end=end,
            listing_views=int(listing_views),
            inquiries_created=int(inquiries_created),
            seller_replied_inquiries=int(seller_replied_inquiries),
            inquiry_reply_rate=float(inquiry_reply_rate),
            listings_with_inquiries=int(listings_with_inquiries),
            listings_sold=int(listings_sold),
            inquiry_to_sold_listing_rate=float(inquiry_to_sold_listing_rate),
            connected_domains=int(connected_domains),
            clicks=int(clicks),
            conversions=int(conversions),
            conversion_rate=float(conversion_rate),
            payouts_paid=int(payouts_paid),
            payouts_paid_amount_total=float(payouts_paid_amount_total),
        )


async def get_cached_window_kpis(days: int) -> Optional[TelemetryWindowKpis]:
    """Return cached KPIs for a window (recompute if TTL expired)."""
    if not settings.enable_business_metrics:
        return None

    now = datetime.utcnow()
    until = _cache_until_by_days.get(days)
    cached = _cache_value_by_days.get(days)
    if until is not None and cached is not None and now < until:
        return cached

    value = await _compute_window_kpis(int(days))
    ttl_seconds = max(5, int(settings.business_metrics_cache_seconds))
    _cache_until_by_days[int(days)] = now + timedelta(seconds=ttl_seconds)
    _cache_value_by_days[int(days)] = value
    return value


# -----------------------------
# Prometheus Gauges
# -----------------------------

if Gauge is not None:
    _g = {
        "deal_listing_views": Gauge("pounce_deal_listing_views", "Deal: listing views in window", ["window_days"]),
        "deal_inquiries_created": Gauge("pounce_deal_inquiries_created", "Deal: inquiries created in window", ["window_days"]),
        "deal_seller_replied_inquiries": Gauge(
            "pounce_deal_seller_replied_inquiries", "Deal: inquiries with seller reply in window", ["window_days"]
        ),
        "deal_inquiry_reply_rate": Gauge("pounce_deal_inquiry_reply_rate", "Deal: inquiry reply rate in window", ["window_days"]),
        "deal_listings_with_inquiries": Gauge(
            "pounce_deal_listings_with_inquiries", "Deal: distinct listings with inquiries in window", ["window_days"]
        ),
        "deal_listings_sold": Gauge("pounce_deal_listings_sold", "Deal: distinct listings marked sold in window", ["window_days"]),
        "deal_inquiry_to_sold_listing_rate": Gauge(
            "pounce_deal_inquiry_to_sold_listing_rate", "Deal: (listings with inquiry) -> sold rate in window", ["window_days"]
        ),
        "yield_connected_domains": Gauge("pounce_yield_connected_domains", "Yield: connected domains in window", ["window_days"]),
        "yield_clicks": Gauge("pounce_yield_clicks", "Yield: clicks in window", ["window_days"]),
        "yield_conversions": Gauge("pounce_yield_conversions", "Yield: conversions in window", ["window_days"]),
        "yield_conversion_rate": Gauge("pounce_yield_conversion_rate", "Yield: conversion rate in window", ["window_days"]),
        "yield_payouts_paid": Gauge("pounce_yield_payouts_paid", "Yield: payouts paid in window", ["window_days"]),
        "yield_payouts_paid_amount_total": Gauge(
            "pounce_yield_payouts_paid_amount_total", "Yield: total amount paid out in window", ["window_days"]
        ),
    }
else:  # pragma: no cover
    _g = {}


async def update_prometheus_business_metrics() -> None:
    """Compute KPIs and set Prometheus gauges (no-op when disabled)."""
    if Gauge is None or not _g:
        return
    if not settings.enable_business_metrics:
        return

    windows = {1, int(settings.business_metrics_days)}
    for days in sorted(windows):
        kpis = await get_cached_window_kpis(days)
        if kpis is None:
            continue
        w = str(int(kpis.window_days))
        _g["deal_listing_views"].labels(window_days=w).set(kpis.listing_views)
        _g["deal_inquiries_created"].labels(window_days=w).set(kpis.inquiries_created)
        _g["deal_seller_replied_inquiries"].labels(window_days=w).set(kpis.seller_replied_inquiries)
        _g["deal_inquiry_reply_rate"].labels(window_days=w).set(kpis.inquiry_reply_rate)
        _g["deal_listings_with_inquiries"].labels(window_days=w).set(kpis.listings_with_inquiries)
        _g["deal_listings_sold"].labels(window_days=w).set(kpis.listings_sold)
        _g["deal_inquiry_to_sold_listing_rate"].labels(window_days=w).set(kpis.inquiry_to_sold_listing_rate)
        _g["yield_connected_domains"].labels(window_days=w).set(kpis.connected_domains)
        _g["yield_clicks"].labels(window_days=w).set(kpis.clicks)
        _g["yield_conversions"].labels(window_days=w).set(kpis.conversions)
        _g["yield_conversion_rate"].labels(window_days=w).set(kpis.conversion_rate)
        _g["yield_payouts_paid"].labels(window_days=w).set(kpis.payouts_paid)
        _g["yield_payouts_paid_amount_total"].labels(window_days=w).set(kpis.payouts_paid_amount_total)

