"""
Prometheus metrics for PPM.

Metric naming convention: ppm_<layer>_<operation>_<unit>

Layers:
  surface   - observer + intent classifier
  nerve     - trajectory predictor
  staging   - prefetch cache + injector
  store     - vector/graph/meta reads and writes
  feedback  - hit/miss detection
  session   - end-to-end turn latency
  write     - memory distillation + storage

All histograms use standard latency buckets in seconds.
All counters are monotonically increasing (never reset).
"""

from prometheus_client import (
    Counter, Histogram, Gauge, Summary,
    CollectorRegistry, generate_latest, CONTENT_TYPE_LATEST,
)

# ── Registry ───────────────────────────────────────────────────────────────────
# Use default registry for simplicity — replace with custom registry
# for multi-process deployments.

REGISTRY = CollectorRegistry()

_LATENCY_BUCKETS = (
    0.001, 0.002, 0.005, 0.010, 0.025, 0.050,
    0.100, 0.250, 0.500, 1.000, 2.500, 5.000,
)

# ── Session metrics ────────────────────────────────────────────────────────────

session_turns_total = Counter(
    "ppm_session_turns_total",
    "Total conversation turns processed",
    ["intent"],
    registry=REGISTRY,
)

session_turn_latency = Histogram(
    "ppm_session_turn_latency_seconds",
    "End-to-end turn latency (hot path only, before post-response)",
    buckets=_LATENCY_BUCKETS,
    registry=REGISTRY,
)

session_active = Gauge(
    "ppm_session_active_total",
    "Number of currently active sessions",
    registry=REGISTRY,
)

session_context_tokens = Histogram(
    "ppm_session_context_tokens",
    "Tokens injected into LLM context per turn",
    buckets=(0, 50, 100, 200, 500, 1000, 2000, 4096),
    registry=REGISTRY,
)

session_memories_injected = Histogram(
    "ppm_session_memories_injected",
    "Number of distinct memories injected per turn",
    buckets=(0, 1, 2, 3, 5, 8, 13),
    registry=REGISTRY,
)

# ── Nerve / Predictor metrics ──────────────────────────────────────────────────

nerve_predictions_generated = Counter(
    "ppm_nerve_predictions_total",
    "Predictions generated by trajectory predictor",
    ["strategy", "intent"],
    registry=REGISTRY,
)

nerve_momentum_velocity = Histogram(
    "ppm_nerve_momentum_velocity",
    "Conversation momentum velocity per turn",
    buckets=(0, 0.02, 0.05, 0.10, 0.20, 0.40, 0.80, 1.60),
    registry=REGISTRY,
)

nerve_context_switches = Counter(
    "ppm_nerve_context_switches_total",
    "Context switch resets detected by observer",
    registry=REGISTRY,
)

nerve_observe_latency = Histogram(
    "ppm_nerve_observe_latency_seconds",
    "Observer.observe() latency (includes embedding call)",
    buckets=_LATENCY_BUCKETS,
    registry=REGISTRY,
)

# ── Staging metrics ────────────────────────────────────────────────────────────

staging_prefetch_latency = Histogram(
    "ppm_staging_prefetch_latency_seconds",
    "Per-slot prefetch latency (background, not on hot path)",
    ["strategy"],
    buckets=_LATENCY_BUCKETS,
    registry=REGISTRY,
)

staging_slot_hits = Counter(
    "ppm_staging_slot_hits_total",
    "Times a staged memory was successfully injected",
    ["tier"],
    registry=REGISTRY,
)

staging_slot_misses = Counter(
    "ppm_staging_slot_misses_total",
    "Times no staged memory was available for injection",
    registry=REGISTRY,
)

staging_evictions = Counter(
    "ppm_staging_evictions_total",
    "Staging slot evictions due to TTL expiry",
    registry=REGISTRY,
)

staging_slots_ready = Gauge(
    "ppm_staging_slots_ready",
    "Number of filled, non-expired staging slots",
    registry=REGISTRY,
)

# ── Store metrics ──────────────────────────────────────────────────────────────

store_vector_search_latency = Histogram(
    "ppm_store_vector_search_latency_seconds",
    "Qdrant vector search latency",
    buckets=_LATENCY_BUCKETS,
    registry=REGISTRY,
)

store_memory_writes_total = Counter(
    "ppm_store_memory_writes_total",
    "Memory write operations",
    ["action"],  # written, duplicate, skipped, error
    registry=REGISTRY,
)

store_chunks_written_total = Counter(
    "ppm_store_chunks_written_total",
    "Total chunks written to vector store",
    registry=REGISTRY,
)

store_outbox_pending = Gauge(
    "ppm_store_outbox_pending",
    "Number of pending outbox entries awaiting propagation",
    registry=REGISTRY,
)

store_outbox_dead_letters = Counter(
    "ppm_store_outbox_dead_letters_total",
    "Outbox entries that exhausted retries and went to dead letter queue",
    registry=REGISTRY,
)

# ── Feedback metrics ───────────────────────────────────────────────────────────

feedback_hit_rate = Histogram(
    "ppm_feedback_hit_rate",
    "Per-turn prediction hit rate",
    buckets=(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0),
    registry=REGISTRY,
)

feedback_hits_total = Counter(
    "ppm_feedback_hits_total",
    "Total prediction hits",
    ["signal"],  # overlap, semantic, prevented
    registry=REGISTRY,
)

feedback_misses_total = Counter(
    "ppm_feedback_misses_total",
    "Total prediction misses",
    ["strategy"],
    registry=REGISTRY,
)

feedback_bandit_confidence = Histogram(
    "ppm_feedback_bandit_confidence",
    "Bandit confidence scores at prediction time",
    ["strategy", "intent"],
    buckets=(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0),
    registry=REGISTRY,
)

trajectory_samples_total = Counter(
    "ppm_trajectory_samples_total",
    "Total trajectory samples recorded for fine-tuning",
    registry=REGISTRY,
)

# ── Write pipeline metrics ─────────────────────────────────────────────────────

write_distill_latency = Histogram(
    "ppm_write_distill_latency_seconds",
    "MemoryDistiller LLM call latency",
    buckets=_LATENCY_BUCKETS,
    registry=REGISTRY,
)

write_candidates_per_turn = Histogram(
    "ppm_write_candidates_per_turn",
    "Memory candidates extracted per turn by distiller",
    buckets=(0, 1, 2, 3, 5),
    registry=REGISTRY,
)


# ── Helpers ────────────────────────────────────────────────────────────────────

def record_turn(intent: str, latency_s: float, tokens: int, memories: int) -> None:
    """Convenience function — record all session-level metrics for one turn."""
    session_turns_total.labels(intent=intent).inc()
    session_turn_latency.observe(latency_s)
    session_context_tokens.observe(tokens)
    session_memories_injected.observe(memories)


def record_feedback(
    hit_rate: float,
    hits: list[dict],   # [{"signal": str}]
    misses: list[dict], # [{"strategy": str}]
) -> None:
    """Record all feedback-layer metrics for one turn."""
    feedback_hit_rate.observe(hit_rate)
    for h in hits:
        feedback_hits_total.labels(signal=h.get("signal", "unknown")).inc()
    for m in misses:
        feedback_misses_total.labels(strategy=m.get("strategy", "unknown")).inc()


def metrics_response() -> tuple[bytes, str]:
    """Return (body, content_type) for /metrics endpoint."""
    return generate_latest(REGISTRY), CONTENT_TYPE_LATEST

