"""Heartbeat: periodic liveness + light metrics to the Central API.

The heartbeat must be resilient: if the API is unavailable the agent logs the
failure and keeps running, retrying on the next interval. It never crashes the
service and never blocks indefinitely (the API client has its own timeout).
"""

from __future__ import annotations

import time
from datetime import datetime, timezone
from typing import Any

from api_client import ApiClient, ApiError
from config import Config
from discovery import system
from identity import load_agent_token, save_status, save_last_error
from logger import get_logger

log = get_logger("heartbeat")

_last_fetch_time = 0.0
_cached_latest_version = None
_cached_update_available = False


def get_update_status(config: Config, api_client: ApiClient) -> tuple[str, bool]:
    global _last_fetch_time, _cached_latest_version, _cached_update_available
    
    now = time.time()
    interval = getattr(config, "heartbeat_interval", 60)
    
    if _cached_latest_version is not None and (now - _last_fetch_time < interval):
        return _cached_latest_version, _cached_update_available
        
    try:
        from update import check as check_update
        info = check_update(config, api_client)
        if info is not None:
            _cached_latest_version = info.latest
            _cached_update_available = info.update_available
            _last_fetch_time = now
    except Exception as exc:
        log.warning("Silent ignore of update manifest fetch error: %s", exc)
        
    if _cached_latest_version is None:
        return config.agent_version, False
        
    return _cached_latest_version, _cached_update_available


def build_payload(config: Config, api_client: ApiClient, status: str = "online") -> dict[str, Any]:
    """Assemble the heartbeat payload.

    The heartbeat contract requires `agent_version`, `update_available`,
    `latest_version`, and `status`; the agent is identified by its
    X-Agent-Token header, not the body. Light metrics are sent as additional
    informational fields.
    """
    latest_version, update_available = get_update_status(config, api_client)
    
    try:
        import metrics
        metrics.start_updater(interval=config.heartbeat_interval)
        collected_metrics = metrics.get_metrics()
    except Exception as exc:
        log.warning("Failed to collect telemetry: %s", exc)
        collected_metrics = {}

    return {
        "agent_version": config.agent_version,
        "update_available": update_available,
        "latest_version": latest_version,
        "status": status,
        "server_id": config.server_id,
        "hostname": system.hostname(),
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "metrics": collected_metrics,
    }


def send_once(config: Config, api_client: ApiClient, status: str = "online") -> bool:
    """Send a single heartbeat. Returns True on success, False on failure.

    Never raises — transport errors are caught and reported so the caller's
    loop continues.
    """
    if config.status == "revoked":
        log.warning("Heartbeat skipped: Agent is revoked.")
        return False

    payload = build_payload(config, api_client, status)
    try:
        api_client.heartbeat(payload, load_agent_token(config))
        log.info("heartbeat ok (status=%s)", status)
        save_last_error(config, None)

        # Check if we have a pending update state to report success
        import json
        state_file = config.var_dir / "update_state.json"
        if state_file.is_file():
            try:
                with open(state_file, "r") as f:
                    update_state = json.load(f)
                
                job_id = update_state.get("job_id")
                if job_id:
                    log.info("Reporting self-update command success to backend for job_id=%s", job_id)
                    api_client.post_command_result(
                        job_id,
                        {"status": "succeeded", "result": {"success": True, "message": f"Successfully updated to {config.agent_version}"}},
                        load_agent_token(config)
                    )
                
                # Cleanup backup dir
                backup_dir_path = update_state.get("backup_dir")
                if backup_dir_path:
                    from pathlib import Path
                    import shutil
                    bd = Path(backup_dir_path)
                    if bd.exists():
                        shutil.rmtree(bd)
                        log.info("Cleaned up backup directory %s", bd)
                
                # Delete update state file
                state_file.unlink()
                log.info("Deleted update state file")
            except Exception as e:
                log.error("Failed to complete update reporting or cleanup: %s", e)
        
        import os
        flag_file = os.environ.get("WOLFPANEL_UPDATE_FLAG_FILE")
        if flag_file:
            try:
                with open(flag_file, "w") as f:
                    f.write("OK")
                log.info("Wrote update success flag file: %s", flag_file)
            except Exception as e:
                log.error("Failed to write update flag file: %s", e)
        parent_pid = os.environ.get("WOLFPANEL_UPDATE_PARENT_PID")
        if parent_pid:
            import signal
            try:
                if hasattr(signal, "SIGUSR1"):
                    os.kill(int(parent_pid), signal.SIGUSR1)
                    log.info("Sent SIGUSR1 to parent PID: %s", parent_pid)
            except Exception as e:
                log.error("Failed to signal parent: %s", e)
                
        return True
    except ApiError as exc:
        save_last_error(config, str(exc))
        if exc.status_code == 401:
            save_status(config, "revoked")
            log.warning("Heartbeat rejected by Central; marked agent revoked")
        else:
            log.warning("heartbeat failed, will retry next interval: %s", exc)
        return False
