"""Daemon mode - discovery, heartbeats, metadata sync."""

import logging
import sys
import time
import signal
from runsentry import __version__
from runsentry.config import get_config
from runsentry.discovery import discover_cron_jobs, discover_k8s_jobs
from runsentry.transport.client import APIClient
from runsentry.storage.inventory import load_inventory, save_inventory, diff_inventory


# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)


_running = True


def _signal_handler(signum, frame):
    """Handle shutdown signals."""
    global _running
    _running = False
    logger.info("Received shutdown signal")


def run_daemon():
    """Run agent daemon."""
    config = get_config()

    if not config.is_configured():
        logger.error("Agent not configured. Run install.sh with RUNSENTRY_TOKEN to register.")
        logger.error("Example: RUNSENTRY_TOKEN=xxx curl -fsSL https://runsentrystorage.blob.core.windows.net/main/install.sh | bash")
        sys.exit(1)

    # Reload config to ensure we have latest state
    config.load()

    # Setup signal handlers
    signal.signal(signal.SIGINT, _signal_handler)
    signal.signal(signal.SIGTERM, _signal_handler)

    client = APIClient()

    logger.info("RunSentry Agent daemon starting...")
    logger.info(f"Agent ID: {config.agent_id}")
    logger.info(f"Backend: {config.backend_url}")
    logger.info(f"Version: {__version__}")

    # Load previous inventory for diffing
    previous_inventory = load_inventory()
    logger.info(f"Loaded {len(previous_inventory)} jobs from previous inventory")

    # Initial discovery
    logger.info("Performing initial job discovery...")
    try:
        all_jobs = []
        all_jobs.extend(discover_cron_jobs())
        all_jobs.extend(discover_k8s_jobs())
        
        # Convert to dict keyed by externalId for diffing
        current_inventory = {job.get("externalId"): job for job in all_jobs if job.get("externalId")}
        
        if current_inventory:
            logger.info(f"Discovered {len(current_inventory)} jobs")
            
            # Diff inventory
            if previous_inventory:
                diff = diff_inventory(previous_inventory, current_inventory)
                logger.info(f"Inventory diff: {len(diff['added'])} added, {len(diff['updated'])} updated, {len(diff['deleted'])} deleted")
                
                # Send only changes
                changes_to_send = []
                if diff["added"]:
                    changes_to_send.extend(diff["added"])
                if diff["updated"]:
                    changes_to_send.extend(diff["updated"])
                if diff["deleted"]:
                    changes_to_send.extend(diff["deleted"])
                
                if changes_to_send:
                    logger.info(f"Sending {len(changes_to_send)} job changes to backend")
                    if client.discover_jobs(changes_to_send):
                        # Only save inventory after successful sync
                        save_inventory(current_inventory)
                        logger.info("Inventory synced and saved")
                    else:
                        logger.warning("Failed to sync inventory changes, will retry on next cycle")
                else:
                    logger.info("No inventory changes detected")
                    # Still save current inventory to update timestamps
                    save_inventory(current_inventory)
            else:
                # First run - send all jobs as added
                logger.info("First run: sending all discovered jobs")
                if client.discover_jobs(list(current_inventory.values())):
                    save_inventory(current_inventory)
                    logger.info("Initial inventory synced and saved")
                else:
                    logger.warning("Failed to sync initial inventory, will retry on next cycle")
        else:
            logger.info("No jobs discovered")
            # If we had jobs before but now have none, send deletion for all
            if previous_inventory:
                logger.info("All jobs deleted, sending deletion notifications")
                deleted_jobs = list(previous_inventory.values())
                if client.discover_jobs(deleted_jobs):
                    save_inventory({})
                    logger.info("Job deletions synced")
    except Exception as e:
        logger.error(f"Error during initial discovery: {e}", exc_info=True)
        # Continue running even if discovery fails

    # Main loop
    discovery_interval = config.discovery_interval
    heartbeat_interval = config.heartbeat_interval
    flush_interval = config.flush_interval
    last_discovery = time.time()
    last_heartbeat = time.time()
    last_flush = time.time()

    logger.info(f"Daemon running (discovery: {discovery_interval}s, heartbeat: {heartbeat_interval}s, flush: {flush_interval}s)")
    logger.info("Press Ctrl+C to stop.")

    try:
        while _running:
            now = time.time()

            # Periodic discovery with diffing
            if now - last_discovery >= discovery_interval:
                try:
                    logger.info("Performing periodic job discovery...")
                    previous_inventory = load_inventory()
                    
                    all_jobs = []
                    all_jobs.extend(discover_cron_jobs())
                    all_jobs.extend(discover_k8s_jobs())
                    
                    # Convert to dict keyed by externalId
                    current_inventory = {job.get("externalId"): job for job in all_jobs if job.get("externalId")}
                    
                    if current_inventory or previous_inventory:
                        # Diff inventory
                        diff = diff_inventory(previous_inventory, current_inventory)
                        
                        if diff["added"] or diff["updated"] or diff["deleted"]:
                            logger.info(f"Inventory diff: {len(diff['added'])} added, {len(diff['updated'])} updated, {len(diff['deleted'])} deleted")
                            
                            # Send only changes
                            changes_to_send = []
                            if diff["added"]:
                                changes_to_send.extend(diff["added"])
                            if diff["updated"]:
                                changes_to_send.extend(diff["updated"])
                            if diff["deleted"]:
                                changes_to_send.extend(diff["deleted"])
                            
                            if client.discover_jobs(changes_to_send):
                                save_inventory(current_inventory)
                                logger.info("Inventory changes synced successfully")
                            else:
                                logger.warning("Failed to sync inventory changes, will retry on next cycle")
                        else:
                            logger.debug("No inventory changes detected")
                            # Still save to update file timestamp
                            save_inventory(current_inventory)
                    else:
                        logger.debug("No jobs discovered")
                        # Clear inventory if we had jobs before
                        if previous_inventory:
                            if client.discover_jobs(list(previous_inventory.values())):
                                save_inventory({})
                                logger.info("All jobs deleted, synced to backend")
                    
                    last_discovery = now
                except Exception as e:
                    logger.error(f"Error during periodic discovery: {e}", exc_info=True)
                    # Continue running after discovery errors

            # Heartbeat
            if now - last_heartbeat >= heartbeat_interval:
                try:
                    if client.send_heartbeat():
                        logger.debug("Heartbeat sent successfully")
                    else:
                        logger.warning("Heartbeat failed")
                    last_heartbeat = now
                except Exception as e:
                    logger.error(f"Error sending heartbeat: {e}", exc_info=True)
                    # Continue running after heartbeat errors

            # Flush buffered events
            if now - last_flush >= flush_interval:
                try:
                    client.flush_buffer()
                    last_flush = now
                except Exception as e:
                    logger.error(f"Error flushing buffer: {e}", exc_info=True)
                    # Continue running after flush errors

            # Sleep for a short interval
            time.sleep(10)

    except KeyboardInterrupt:
        logger.info("Interrupted by user")
    except Exception as e:
        logger.error(f"Fatal error in daemon loop: {e}", exc_info=True)
        raise

    logger.info("Shutting down daemon...")
    # Final flush
    try:
        client.flush_buffer()
    except Exception as e:
        logger.error(f"Error during final flush: {e}", exc_info=True)
    logger.info("Daemon stopped.")

