"""Agent heartbeat aging and Host status projection."""
from __future__ import annotations

import asyncio
import datetime as dt
import logging
from contextlib import suppress
from typing import Optional

from sqlalchemy import select

from ..config import TimingPolicy, get_settings
from ..db import session_scope
from ..enums import HostStatus
from ..models import TestHost, utcnow

logger = logging.getLogger(__name__)


def projected_agent_status(
    last_heartbeat_at: Optional[dt.datetime],
    current_status: str,
    *,
    now: Optional[dt.datetime] = None,
    timing: Optional[TimingPolicy] = None,
) -> str:
    """Return the status implied by heartbeat age.

    A fresh explicit DEGRADED report remains degraded until the Agent sends a
    healthy heartbeat. Only Agents with an ``agent_id`` use this projection;
    seeded simulator Hosts retain their configured status.
    """

    if last_heartbeat_at is None:
        return HostStatus.OFFLINE.value
    policy = timing or get_settings().timing
    age_s = max(0.0, ((now or utcnow()) - last_heartbeat_at).total_seconds())
    if age_s >= policy.heartbeat_lost_after_s:
        return HostStatus.OFFLINE.value
    if age_s >= policy.heartbeat_degraded_after_s:
        return HostStatus.DEGRADED.value
    if current_status == HostStatus.DEGRADED.value:
        return HostStatus.DEGRADED.value
    return HostStatus.ONLINE.value


def refresh_agent_host(host: TestHost, *, now: Optional[dt.datetime] = None) -> str:
    """Refresh one registered Host in the caller's transaction."""

    if not host.agent_id:
        return host.status
    host.status = projected_agent_status(
        host.last_heartbeat_at,
        host.status,
        now=now,
    )
    return host.status


async def refresh_all_agent_hosts(*, now: Optional[dt.datetime] = None) -> int:
    """Persist heartbeat-derived states for all registered Agents."""

    changed = 0
    async with session_scope() as session:
        hosts = (
            await session.scalars(
                select(TestHost).where(TestHost.agent_id.is_not(None))
            )
        ).all()
        for host in hosts:
            previous = host.status
            refresh_agent_host(host, now=now)
            if host.status != previous:
                changed += 1
    return changed


async def run_agent_watchdog() -> None:
    """Continuously age registered Agent heartbeats."""

    interval_s = max(1.0, get_settings().timing.heartbeat_interval_s)
    while True:
        try:
            await refresh_all_agent_hosts()
        except asyncio.CancelledError:
            raise
        except Exception:
            logger.exception("Agent heartbeat watchdog failed")
        await asyncio.sleep(interval_s)


async def stop_agent_watchdog(task: "asyncio.Task[None]") -> None:
    task.cancel()
    with suppress(asyncio.CancelledError):
        await task
