"""Heartbeat service for periodic health reporting."""

from __future__ import annotations

import asyncio
import contextlib
import time
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any

import structlog

from curadise_agent.models.api.heartbeat import HeartbeatRequest, HeartbeatResponse

if TYPE_CHECKING:
    from curadise_agent.config.schema import HeartbeatConfig
    from curadise_agent.heartbeat.health import HealthChecker
    from curadise_agent.state.manager import StateManager
    from curadise_agent.transport.client import TransportClient
    from curadise_agent.utils.system_info import SystemInfo

log = structlog.get_logger(__name__)


@dataclass
class HeartbeatStats:
    """Statistics about heartbeat operations."""

    total_sent: int = 0
    successful: int = 0
    failed: int = 0
    last_sent: datetime | None = None
    last_success: datetime | None = None
    last_failure: datetime | None = None
    consecutive_failures: int = 0


@dataclass
class HeartbeatService:
    """
    Manages periodic heartbeat sending to the server.

    The heartbeat includes:
    - Agent status and health
    - Resource metrics summary
    - Configuration sync requests
    """

    transport: TransportClient
    state_manager: StateManager
    health_checker: HealthChecker
    config: HeartbeatConfig
    system_info: SystemInfo | None = None
    tags: dict[str, str] = field(default_factory=dict)
    on_config_update: Any = None  # Callback for config updates
    on_commands_pending: Any = None  # Callback when commands available
    _stats: HeartbeatStats = field(default_factory=HeartbeatStats, init=False)
    _running: bool = field(default=False, init=False)
    _task: asyncio.Task[None] | None = field(default=None, init=False)
    _start_time: float = field(default_factory=time.monotonic, init=False)

    @property
    def stats(self) -> HeartbeatStats:
        """Get heartbeat statistics."""
        return self._stats

    @property
    def is_running(self) -> bool:
        """Check if heartbeat service is running."""
        return self._running

    @property
    def uptime_seconds(self) -> float:
        """Get agent uptime in seconds."""
        return time.monotonic() - self._start_time

    async def start(self, shutdown_event: asyncio.Event) -> None:
        """
        Start the heartbeat service.

        Args:
            shutdown_event: Event to signal shutdown
        """
        if self._running:
            return

        self._running = True
        self._start_time = time.monotonic()
        self._task = asyncio.create_task(
            self._heartbeat_loop(shutdown_event),
            name="heartbeat-service",
        )

        log.info("heartbeat_service_started", interval=self.config.interval)

    async def stop(self) -> None:
        """Stop the heartbeat service."""
        if not self._running:
            return

        self._running = False

        if self._task:
            self._task.cancel()
            with contextlib.suppress(asyncio.CancelledError):
                await self._task
            self._task = None

        log.info("heartbeat_service_stopped")

    async def _heartbeat_loop(self, shutdown_event: asyncio.Event) -> None:
        """Main heartbeat loop."""
        # Send initial heartbeat immediately
        await self._send_heartbeat()

        while self._running:
            try:
                # Wait for interval or shutdown
                await asyncio.wait_for(
                    shutdown_event.wait(),
                    timeout=self.config.interval,
                )
                # Shutdown signaled
                break
            except TimeoutError:
                # Normal timeout - send heartbeat
                await self._send_heartbeat()

    async def _send_heartbeat(self) -> HeartbeatResponse | None:
        """Send a single heartbeat."""
        if not self.state_manager.is_registered:
            log.debug("heartbeat_skipped_not_registered")
            return None

        agent_id = self.state_manager.agent_id
        if not agent_id:
            return None

        # Build heartbeat request
        health_summary = self.health_checker.get_summary()

        request = HeartbeatRequest(
            agent_id=agent_id,
            state=self.state_manager.state.value,
            uptime_seconds=self.uptime_seconds,
            metrics_summary=health_summary if self.config.include_metrics else {},
            system_info=self.system_info.to_dict() if self.system_info else {},
            tags=self.tags,
        )

        self._stats.total_sent += 1
        self._stats.last_sent = datetime.now(UTC)

        try:
            response_data = await asyncio.wait_for(
                self.transport.send_heartbeat(request.to_dict()),
                timeout=self.config.timeout,
            )

            response = HeartbeatResponse.from_dict(response_data)

            self._stats.successful += 1
            self._stats.last_success = datetime.now(UTC)
            self._stats.consecutive_failures = 0

            log.debug(
                "heartbeat_sent",
                agent_id=agent_id,
                pending_commands=response.pending_commands,
            )

            # Handle response
            await self._handle_response(response)

            return response

        except TimeoutError:
            self._stats.failed += 1
            self._stats.last_failure = datetime.now(UTC)
            self._stats.consecutive_failures += 1
            log.warning("heartbeat_timeout", consecutive_failures=self._stats.consecutive_failures)
            return None

        except Exception as e:
            self._stats.failed += 1
            self._stats.last_failure = datetime.now(UTC)
            self._stats.consecutive_failures += 1
            log.error(
                "heartbeat_failed",
                error=str(e),
                consecutive_failures=self._stats.consecutive_failures,
            )
            return None

    async def _handle_response(self, response: HeartbeatResponse) -> None:
        """Handle heartbeat response from server."""
        # Log any messages
        for message in response.messages:
            log.info("server_message", message=message)

        # Handle config update
        if response.config_update and self.on_config_update:
            log.info(
                "config_update_available",
                version=response.config_version,
            )
            try:
                await self.on_config_update(response.config_update, response.config_version)
            except Exception as e:
                log.error("config_update_failed", error=str(e))

        # Handle pending commands
        if response.pending_commands > 0 and self.on_commands_pending:
            log.info("commands_pending", count=response.pending_commands)
            try:
                await self.on_commands_pending(response.pending_commands)
            except Exception as e:
                log.error("commands_fetch_failed", error=str(e))

    async def send_immediate(self) -> HeartbeatResponse | None:
        """Send an immediate heartbeat outside the regular schedule."""
        return await self._send_heartbeat()

    def get_status(self) -> dict[str, Any]:
        """Get heartbeat service status."""
        return {
            "running": self._running,
            "interval": self.config.interval,
            "uptime_seconds": self.uptime_seconds,
            "stats": {
                "total_sent": self._stats.total_sent,
                "successful": self._stats.successful,
                "failed": self._stats.failed,
                "consecutive_failures": self._stats.consecutive_failures,
                "last_sent": self._stats.last_sent.isoformat() if self._stats.last_sent else None,
                "last_success": (
                    self._stats.last_success.isoformat() if self._stats.last_success else None
                ),
            },
        }
