#!/usr/bin/env python3
"""
Firefrost Gaming - Staggered Server Restart System
Automated daily/weekly restart system for Minecraft servers via Pterodactyl API

Author: Michael "Frostystyle" Krause & Claude "The Chronicler"
Version: 1.0.0
Date: 2026-02-17
"""

import json
import time
import logging
import sys
from datetime import datetime
from pathlib import Path

try:
    import requests
except ImportError:
    print("ERROR: requests module not installed. Run: pip3 install requests --break-system-packages")
    sys.exit(1)

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('/var/log/staggered-restart.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)


class StaggeredRestartSystem:
    def __init__(self, config_path='/opt/automation/restart-config.json'):
        """Initialize the restart system with configuration"""
        self.config = self.load_config(config_path)
        self.ptero_url = self.config['pterodactyl']['url']
        self.ptero_key = self.config['pterodactyl']['api_key']
        self.discord_webhook = self.config['discord']['webhook_url']
        self.discord_enabled = self.config['discord']['notifications_enabled']
        self.settings = self.config['restart_settings']
        self.servers = sorted(self.config['servers'], key=lambda x: x['priority'])
        
        self.results = {
            'successful': [],
            'failed': [],
            'skipped': []
        }
    
    def load_config(self, path):
        """Load configuration from JSON file"""
        try:
            with open(path, 'r') as f:
                return json.load(f)
        except FileNotFoundError:
            logger.error(f"Config file not found: {path}")
            sys.exit(1)
        except json.JSONDecodeError as e:
            logger.error(f"Invalid JSON in config file: {e}")
            sys.exit(1)
    
    def api_request(self, endpoint, method='GET', data=None):
        """Make request to Pterodactyl API"""
        url = f"{self.ptero_url}/api/client/{endpoint}"
        headers = {
            'Authorization': f'Bearer {self.ptero_key}',
            'Accept': 'application/vnd.pterodactyl.v1+json',
            'Content-Type': 'application/json'
        }
        
        try:
            if method == 'GET':
                response = requests.get(url, headers=headers, timeout=30)
            elif method == 'POST':
                response = requests.post(url, headers=headers, json=data, timeout=30)
            
            response.raise_for_status()
            return response.json() if response.text else {}
        
        except requests.exceptions.RequestException as e:
            logger.error(f"API request failed: {e}")
            return None
    
    def get_server_status(self, uuid):
        """Get server status from Pterodactyl"""
        data = self.api_request(f"servers/{uuid}/resources")
        if data and 'attributes' in data:
            return data['attributes']['current_state']
        return None
    
    def get_player_count(self, uuid):
        """Get current player count (approximation via resource usage)"""
        # Note: Pterodactyl doesn't directly expose player count
        # This is a placeholder - may need RCON integration for accurate count
        data = self.api_request(f"servers/{uuid}/resources")
        if data and 'attributes' in data:
            # Estimate based on memory usage or implement RCON query
            return 0  # Placeholder
        return 0
    
    def send_console_command(self, uuid, command):
        """Send command to server console"""
        data = {'command': command}
        result = self.api_request(f"servers/{uuid}/command", method='POST', data=data)
        return result is not None
    
    def send_warning(self, uuid, server_name, seconds):
        """Send restart warning to server"""
        if seconds >= 60:
            minutes = seconds // 60
            message = f"say Server restart in {minutes} minute{'s' if minutes != 1 else ''}! Save your work!"
        else:
            message = f"say Server restart in {seconds} seconds! Save NOW!"
        
        logger.info(f"{server_name}: Sending {seconds}s warning")
        return self.send_console_command(uuid, message)
    
    def restart_server(self, uuid, server_name):
        """Restart a server via Pterodactyl API"""
        logger.info(f"{server_name}: Initiating restart")
        
        # Send restart signal
        data = {'signal': 'restart'}
        result = self.api_request(f"servers/{uuid}/power", method='POST', data=data)
        
        if result is None:
            logger.error(f"{server_name}: Restart API call failed")
            return False
        
        logger.info(f"{server_name}: Restart signal sent")
        return True
    
    def wait_for_online(self, uuid, server_name, timeout=300):
        """Wait for server to come back online"""
        logger.info(f"{server_name}: Waiting for server to come online")
        start_time = time.time()
        
        while (time.time() - start_time) < timeout:
            status = self.get_server_status(uuid)
            
            if status == 'running':
                duration = int(time.time() - start_time)
                logger.info(f"{server_name}: Server online after {duration} seconds")
                return True
            
            time.sleep(10)  # Check every 10 seconds
        
        logger.error(f"{server_name}: Server failed to come online within {timeout} seconds")
        return False
    
    def discord_notify(self, message, color=None):
        """Send notification to Discord webhook"""
        if not self.discord_enabled or not self.discord_webhook:
            return
        
        embed = {
            'description': message,
            'timestamp': datetime.utcnow().isoformat()
        }
        
        if color:
            embed['color'] = color  # Decimal color code
        
        payload = {
            'embeds': [embed]
        }
        
        try:
            requests.post(self.discord_webhook, json=payload, timeout=10)
        except requests.exceptions.RequestException as e:
            logger.error(f"Discord notification failed: {e}")
    
    def restart_with_warnings(self, server):
        """Restart a server with player warnings"""
        name = server['name']
        uuid = server['uuid']
        
        logger.info(f"=== Starting restart sequence for {name} ===")
        
        # Check if we should skip based on player count
        if self.settings['skip_if_players']:
            player_count = self.get_player_count(uuid)
            if player_count >= self.settings['player_threshold']:
                logger.info(f"{name}: Skipped - {player_count} players online")
                self.results['skipped'].append(name)
                self.discord_notify(f"⏭️ **{name}** - Skipped (players online)", color=16776960)  # Yellow
                return False
        
        # Send warnings at configured intervals
        for interval in self.settings['warning_intervals']:
            self.send_warning(uuid, name, interval)
            time.sleep(1)  # Brief delay between warnings
        
        # Perform restart
        if not self.restart_server(uuid, name):
            logger.error(f"{name}: Restart failed")
            self.results['failed'].append(name)
            self.discord_notify(f"❌ **{name}** - Restart failed", color=16711680)  # Red
            return False
        
        # Wait for server to come back online
        if not self.wait_for_online(uuid, name):
            logger.error(f"{name}: Failed to come online")
            self.results['failed'].append(name)
            self.discord_notify(f"❌ **{name}** - Failed to come online", color=16711680)  # Red
            return False
        
        # Success
        self.results['successful'].append(name)
        self.discord_notify(f"✅ **{name}** - Restarted successfully", color=65280)  # Green
        return True
    
    def run(self):
        """Main restart cycle"""
        logger.info("=" * 60)
        logger.info("STAGGERED RESTART SYSTEM STARTED")
        logger.info(f"Servers to restart: {len(self.servers)}")
        logger.info("=" * 60)
        
        # Send start notification
        start_time = datetime.now()
        estimated_duration = len(self.servers) * (self.settings['delay_between_restarts'] + 180) // 60
        self.discord_notify(
            f"🔄 **Staggered Server Restart Started**\n"
            f"Servers: {len(self.servers)}\n"
            f"Estimated duration: ~{estimated_duration} minutes",
            color=3447003  # Blue
        )
        
        # Restart each server
        for i, server in enumerate(self.servers, 1):
            name = server['name']
            logger.info(f"\n[{i}/{len(self.servers)}] Processing: {name}")
            
            # Restart with warnings
            success = self.restart_with_warnings(server)
            
            # Wait before next server (except last one)
            if i < len(self.servers):
                delay = self.settings['delay_between_restarts']
                logger.info(f"Waiting {delay} seconds before next server...")
                time.sleep(delay)
        
        # Summary
        duration = (datetime.now() - start_time).total_seconds() / 60
        logger.info("\n" + "=" * 60)
        logger.info("RESTART CYCLE COMPLETE")
        logger.info(f"Successful: {len(self.results['successful'])}")
        logger.info(f"Failed: {len(self.results['failed'])}")
        logger.info(f"Skipped: {len(self.results['skipped'])}")
        logger.info(f"Duration: {duration:.1f} minutes")
        logger.info("=" * 60)
        
        # Send completion notification
        status_emoji = "✅" if len(self.results['failed']) == 0 else "⚠️"
        summary = (
            f"{status_emoji} **Restart Cycle Complete**\n"
            f"Successful: {len(self.results['successful'])}\n"
            f"Failed: {len(self.results['failed'])}\n"
            f"Skipped: {len(self.results['skipped'])}\n"
            f"Duration: {duration:.1f} minutes"
        )
        
        if self.results['failed']:
            summary += f"\n\n❌ **Failed Servers:**\n" + "\n".join(f"- {s}" for s in self.results['failed'])
        
        color = 65280 if len(self.results['failed']) == 0 else 16776960  # Green or Yellow
        self.discord_notify(summary, color=color)


if __name__ == '__main__':
    try:
        restart_system = StaggeredRestartSystem()
        restart_system.run()
    except KeyboardInterrupt:
        logger.info("\nRestart cycle interrupted by user")
        sys.exit(0)
    except Exception as e:
        logger.error(f"Unexpected error: {e}", exc_info=True)
        sys.exit(1)
