"""System invariants exposed as metrics.

These queries should return 0 in a healthy system. Non-zero values
indicate potential issues that need investigation.
"""

from __future__ import annotations

from dataclasses import asdict, dataclass
from typing import TYPE_CHECKING

from brawny.logging import get_logger
from brawny.metrics import (
    INVARIANT_NONCE_GAP_AGE,
    INVARIANT_ORPHANED_CLAIMS,
    INVARIANT_ORPHANED_NONCES,
    INVARIANT_PENDING_NO_ATTEMPTS,
    INVARIANT_STUCK_CLAIMED,
    get_metrics,
)

if TYPE_CHECKING:
    from brawny.db.base import Database

logger = get_logger(__name__)


@dataclass
class InvariantMetrics:
    """Current values of all invariant checks."""

    stuck_claimed_intents: int
    nonce_gap_oldest_age_seconds: float
    pending_without_attempts: int
    orphaned_claims: int
    orphaned_nonces: int


def collect_invariants(db: Database, chain_id: int) -> InvariantMetrics:
    """Collect all invariant metrics for a chain.

    Call periodically (e.g., every 30 seconds) to update Prometheus gauges.
    Uses DB methods added in Phase 1 (count_pending_without_attempts) and
    Phase 2 (count_stuck_claimed, etc.).
    """
    m = InvariantMetrics(
        stuck_claimed_intents=db.count_stuck_claimed(chain_id),
        nonce_gap_oldest_age_seconds=db.get_oldest_nonce_gap_age_seconds(chain_id),
        pending_without_attempts=db.count_pending_without_attempts(chain_id),
        orphaned_claims=db.count_orphaned_claims(chain_id),
        orphaned_nonces=db.count_orphaned_nonces(chain_id),
    )

    # Export to Prometheus using metric constants
    metrics = get_metrics()
    metrics.gauge(INVARIANT_STUCK_CLAIMED).set(
        m.stuck_claimed_intents, chain_id=chain_id
    )
    metrics.gauge(INVARIANT_NONCE_GAP_AGE).set(
        m.nonce_gap_oldest_age_seconds, chain_id=chain_id
    )
    metrics.gauge(INVARIANT_PENDING_NO_ATTEMPTS).set(
        m.pending_without_attempts, chain_id=chain_id
    )
    metrics.gauge(INVARIANT_ORPHANED_CLAIMS).set(
        m.orphaned_claims, chain_id=chain_id
    )
    metrics.gauge(INVARIANT_ORPHANED_NONCES).set(
        m.orphaned_nonces, chain_id=chain_id
    )

    # Log if any non-zero
    if any([
        m.stuck_claimed_intents,
        m.pending_without_attempts,
        m.orphaned_claims,
        m.orphaned_nonces,
    ]):
        logger.warning("invariants.violations_detected", **asdict(m), chain_id=chain_id)

    return m
