#!/usr/bin/env python3
"""
Fixture Manager Daemon
A comprehensive Python daemon system for Linux servers with secure web dashboard
and RESTful API with robust authentication mechanisms.
"""

import os
import sys
import signal
import time
import logging
import argparse
import threading
from pathlib import Path
from datetime import datetime
from daemon import DaemonContext
from daemon.pidfile import TimeoutPIDLockFile
import lockfile
import psutil
from flask import Flask
from werkzeug.serving import make_server
import click

# Add the project root to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))

from app import create_app, db
from app.database import init_database_manager
from app.utils.logging import setup_logging, log_daemon_event
from config import config

class FixtureDaemon:
    """Main daemon class for the Fixture Manager system"""
    
    def __init__(self, config_name='production'):
        self.config_name = config_name
        self.app = None
        self.server = None
        self.running = False
        self.shutdown_event = threading.Event()
        self.logger = None
        
    def setup_logging(self):
        """Setup daemon logging"""
        log_file = config[self.config_name].DAEMON_LOG_FILE
        log_level = getattr(logging, config[self.config_name].LOG_LEVEL.upper())
        
        # Create log directory if it doesn't exist
        os.makedirs(os.path.dirname(log_file), exist_ok=True)
        
        # Configure logging
        logging.basicConfig(
            level=log_level,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler(log_file),
                logging.StreamHandler(sys.stdout)
            ]
        )
        
        self.logger = logging.getLogger('fixture-daemon')
        self.logger.info("Daemon logging initialized")
    
    def create_flask_app(self):
        """Create and configure Flask application"""
        try:
            self.app = create_app(self.config_name)
            
            # Initialize database manager (Flask app already created tables)
            db_manager = init_database_manager(self.app)
            
            # Test database connection and create database if needed
            with self.app.app_context():
                if not db_manager.test_connection():
                    self.logger.warning("Database connection test failed - attempting to create database")
                    if not db_manager.create_database_if_not_exists():
                        self.logger.error("Failed to create database - continuing without database")
                        # Continue without database - the app will show setup instructions
                    else:
                        # Test connection again after creating database
                        if db_manager.test_connection():
                            self.logger.info("Database created and connected successfully")
                            # Initialize database with tables
                            db_manager.initialize_database()
                        else:
                            self.logger.error("Database connection still failed after creation")
                else:
                    self.logger.info("Database connection successful")
                    # Create default admin user if needed
                    try:
                        db_manager.create_default_admin()
                    except Exception as e:
                        self.logger.warning(f"Failed to create default admin user: {str(e)}")
            
            self.logger.info("Flask application created successfully")
            return True
            
        except Exception as e:
            self.logger.error(f"Failed to create Flask application: {str(e)}")
            return False
    
    def create_server(self):
        """Create HTTP server"""
        try:
            host = self.app.config['HOST']
            port = self.app.config['PORT']
            
            self.server = make_server(
                host, port, self.app,
                threaded=True,
                request_handler=None
            )
            
            self.logger.info(f"HTTP server created on {host}:{port}")
            return True
            
        except Exception as e:
            self.logger.error(f"Failed to create HTTP server: {str(e)}")
            return False
    
    def signal_handler(self, signum, frame):
        """Handle shutdown signals"""
        signal_names = {
            signal.SIGTERM: 'SIGTERM',
            signal.SIGINT: 'SIGINT',
            signal.SIGHUP: 'SIGHUP'
        }
        
        signal_name = signal_names.get(signum, f'Signal {signum}')
        self.logger.info(f"Received {signal_name}, initiating shutdown...")
        
        if signum == signal.SIGHUP:
            # Reload configuration on SIGHUP
            self.reload_config()
        else:
            # Shutdown on SIGTERM/SIGINT
            self.shutdown()
    
    def reload_config(self):
        """Reload daemon configuration"""
        try:
            self.logger.info("Reloading configuration...")
            # In a production system, you might want to reload config here
            log_daemon_event('CONFIG_RELOAD', 'Configuration reloaded successfully')
        except Exception as e:
            self.logger.error(f"Configuration reload failed: {str(e)}")
            log_daemon_event('CONFIG_RELOAD_FAILED', f'Configuration reload failed: {str(e)}', 'ERROR')
    
    def shutdown(self):
        """Graceful shutdown"""
        self.logger.info("Initiating graceful shutdown...")
        self.running = False
        self.shutdown_event.set()
        
        if self.server:
            self.logger.info("Shutting down HTTP server...")
            self.server.shutdown()
        
        log_daemon_event('DAEMON_SHUTDOWN', 'Daemon shutdown completed')
    
    def run_server(self):
        """Run the HTTP server in a separate thread"""
        try:
            self.logger.info("Starting HTTP server...")
            self.server.serve_forever()
        except Exception as e:
            if self.running:  # Only log if not shutting down
                self.logger.error(f"HTTP server error: {str(e)}")
                log_daemon_event('SERVER_ERROR', f'HTTP server error: {str(e)}', 'ERROR')
    
    def run_maintenance_tasks(self):
        """Run periodic maintenance tasks"""
        try:
            with self.app.app_context():
                from app.database import get_db_manager
                from app.upload.file_handler import get_file_upload_handler
                
                db_manager = get_db_manager()
                file_handler = get_file_upload_handler()
                
                # Clean up expired sessions (every hour)
                if int(time.time()) % 3600 == 0:
                    expired_sessions = db_manager.cleanup_expired_sessions()
                    if expired_sessions > 0:
                        self.logger.info(f"Cleaned up {expired_sessions} expired sessions")
                
                # Clean up failed uploads (every 6 hours)
                if int(time.time()) % 21600 == 0:
                    file_handler.cleanup_failed_uploads()
                    self.logger.info("Cleaned up failed uploads")
                
                # Clean up old logs (daily at midnight)
                current_time = datetime.now()
                if current_time.hour == 0 and current_time.minute == 0:
                    old_logs = db_manager.cleanup_old_logs(30)  # Keep 30 days
                    if old_logs > 0:
                        self.logger.info(f"Cleaned up {old_logs} old log entries")
        
        except Exception as e:
            self.logger.error(f"Maintenance task error: {str(e)}")
    
    def run(self):
        """Main daemon run loop"""
        try:
            self.setup_logging()
            log_daemon_event('DAEMON_START', 'Fixture daemon starting up')
            
            # Create Flask app
            if not self.create_flask_app():
                log_daemon_event('DAEMON_START_FAILED', 'Flask app creation failed', 'ERROR')
                return False
            
            # Create HTTP server
            if not self.create_server():
                log_daemon_event('DAEMON_START_FAILED', 'HTTP server creation failed', 'ERROR')
                return False
            
            # Setup signal handlers
            signal.signal(signal.SIGTERM, self.signal_handler)
            signal.signal(signal.SIGINT, self.signal_handler)
            signal.signal(signal.SIGHUP, self.signal_handler)
            
            # Start HTTP server in separate thread
            server_thread = threading.Thread(target=self.run_server, daemon=True)
            server_thread.start()
            
            self.running = True
            log_daemon_event('DAEMON_STARTED', f'Daemon started successfully on {self.app.config["HOST"]}:{self.app.config["PORT"]}')
            
            # Main loop
            while self.running:
                try:
                    # Run maintenance tasks
                    self.run_maintenance_tasks()
                    
                    # Sleep for 60 seconds or until shutdown
                    if self.shutdown_event.wait(timeout=60):
                        break
                        
                except KeyboardInterrupt:
                    self.logger.info("Keyboard interrupt received")
                    break
                except Exception as e:
                    self.logger.error(f"Main loop error: {str(e)}")
                    time.sleep(5)  # Brief pause before continuing
            
            # Wait for server thread to finish
            if server_thread.is_alive():
                server_thread.join(timeout=10)
            
            self.logger.info("Daemon shutdown complete")
            return True
            
        except Exception as e:
            self.logger.error(f"Daemon run error: {str(e)}")
            log_daemon_event('DAEMON_ERROR', f'Daemon error: {str(e)}', 'ERROR')
            return False

def get_daemon_pid(pid_file):
    """Get daemon PID from PID file"""
    try:
        if os.path.exists(pid_file):
            with open(pid_file, 'r') as f:
                return int(f.read().strip())
    except (ValueError, IOError):
        pass
    return None

def is_daemon_running(pid):
    """Check if daemon is running"""
    if pid is None:
        return False
    
    try:
        return psutil.pid_exists(pid)
    except:
        return False

def start_daemon(config_name='production', foreground=False):
    """Start the daemon"""
    daemon_config = config[config_name]
    pid_file = daemon_config.DAEMON_PID_FILE
    working_dir = daemon_config.DAEMON_WORKING_DIR
    
    # Check if already running
    existing_pid = get_daemon_pid(pid_file)
    if is_daemon_running(existing_pid):
        print(f"Daemon already running with PID {existing_pid}")
        return False
    
    # Create working directory
    os.makedirs(working_dir, exist_ok=True)
    os.makedirs(os.path.dirname(pid_file), exist_ok=True)
    
    daemon = FixtureDaemon(config_name)
    
    if foreground:
        # Run in foreground
        print("Starting daemon in foreground mode...")
        return daemon.run()
    else:
        # Run as daemon
        print("Starting daemon in background mode...")
        
        # Create daemon context
        pidfile = TimeoutPIDLockFile(pid_file, timeout=10)
        
        context = DaemonContext(
            pidfile=pidfile,
            working_directory=working_dir,
            umask=0o002,
            prevent_core=True,
        )
        
        try:
            with context:
                return daemon.run()
        except lockfile.AlreadyLocked:
            print("Daemon is already running")
            return False
        except Exception as e:
            print(f"Failed to start daemon: {str(e)}")
            return False

def stop_daemon(config_name='production'):
    """Stop the daemon"""
    daemon_config = config[config_name]
    pid_file = daemon_config.DAEMON_PID_FILE
    
    pid = get_daemon_pid(pid_file)
    if not is_daemon_running(pid):
        print("Daemon is not running")
        return True
    
    try:
        print(f"Stopping daemon (PID {pid})...")
        os.kill(pid, signal.SIGTERM)
        
        # Wait for daemon to stop
        for _ in range(30):  # Wait up to 30 seconds
            if not is_daemon_running(pid):
                print("Daemon stopped successfully")
                # Clean up PID file
                try:
                    os.remove(pid_file)
                except OSError:
                    pass
                return True
            time.sleep(1)
        
        # Force kill if still running
        print("Daemon did not stop gracefully, forcing termination...")
        os.kill(pid, signal.SIGKILL)
        time.sleep(2)
        
        if not is_daemon_running(pid):
            print("Daemon forcefully terminated")
            try:
                os.remove(pid_file)
            except OSError:
                pass
            return True
        else:
            print("Failed to stop daemon")
            return False
            
    except ProcessLookupError:
        print("Daemon process not found")
        try:
            os.remove(pid_file)
        except OSError:
            pass
        return True
    except PermissionError:
        print("Permission denied - run as root or daemon owner")
        return False
    except Exception as e:
        print(f"Error stopping daemon: {str(e)}")
        return False

def restart_daemon(config_name='production'):
    """Restart the daemon"""
    print("Restarting daemon...")
    stop_daemon(config_name)
    time.sleep(2)
    return start_daemon(config_name)

def reload_daemon(config_name='production'):
    """Reload daemon configuration"""
    daemon_config = config[config_name]
    pid_file = daemon_config.DAEMON_PID_FILE
    
    pid = get_daemon_pid(pid_file)
    if not is_daemon_running(pid):
        print("Daemon is not running")
        return False
    
    try:
        print(f"Reloading daemon configuration (PID {pid})...")
        os.kill(pid, signal.SIGHUP)
        print("Reload signal sent successfully")
        return True
    except Exception as e:
        print(f"Error reloading daemon: {str(e)}")
        return False

def status_daemon(config_name='production'):
    """Check daemon status"""
    daemon_config = config[config_name]
    pid_file = daemon_config.DAEMON_PID_FILE
    
    pid = get_daemon_pid(pid_file)
    if is_daemon_running(pid):
        try:
            process = psutil.Process(pid)
            print(f"Daemon is running (PID {pid})")
            print(f"  Status: {process.status()}")
            print(f"  CPU: {process.cpu_percent()}%")
            print(f"  Memory: {process.memory_info().rss / 1024 / 1024:.1f} MB")
            print(f"  Started: {datetime.fromtimestamp(process.create_time())}")
            return True
        except psutil.NoSuchProcess:
            print("Daemon PID file exists but process not found")
            return False
    else:
        print("Daemon is not running")
        return False

@click.command()
@click.argument('action', type=click.Choice(['start', 'stop', 'restart', 'reload', 'status']))
@click.option('--config', '-c', default='production', help='Configuration name')
@click.option('--foreground', '-f', is_flag=True, help='Run in foreground (for start action)')
def main(action, config, foreground):
    """Fixture Manager Daemon Control Script"""
    
    if action == 'start':
        success = start_daemon(config, foreground)
        sys.exit(0 if success else 1)
    elif action == 'stop':
        success = stop_daemon(config)
        sys.exit(0 if success else 1)
    elif action == 'restart':
        success = restart_daemon(config)
        sys.exit(0 if success else 1)
    elif action == 'reload':
        success = reload_daemon(config)
        sys.exit(0 if success else 1)
    elif action == 'status':
        success = status_daemon(config)
        sys.exit(0 if success else 1)

if __name__ == '__main__':
    main()  # pylint: disable=no-value-for-parameter